123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- package com.vaadin.tests.server;
-
- import static org.junit.Assert.fail;
-
- import java.io.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.File;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- import java.lang.reflect.Constructor;
- import java.lang.reflect.Field;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.lang.reflect.Modifier;
- import java.lang.reflect.Type;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.Enumeration;
- import java.util.List;
- import java.util.Optional;
- import java.util.jar.JarEntry;
- import java.util.jar.JarFile;
- import java.util.stream.Collectors;
- import java.util.stream.Stream;
-
- import org.junit.Test;
-
- import com.vaadin.ui.Component;
-
- public class ClassesSerializableTest {
-
- /**
- * JARs that will be scanned for classes to test, in addition to classpath
- * directories.
- */
- private static final String JAR_PATTERN = ".*vaadin.*\\.jar";
-
- private static final String[] BASE_PACKAGES = { "com.vaadin" };
-
- private static final String[] EXCLUDED_PATTERNS = {
- "com\\.vaadin\\.demo\\..*", //
- "com\\.vaadin\\.external\\.org\\.apache\\.commons\\.fileupload\\..*", //
- "com\\.vaadin\\.launcher\\..*", //
- "com\\.vaadin\\.client\\..*", //
- "com\\.vaadin\\.server\\.widgetsetutils\\..*", //
- "com\\.vaadin\\.server\\.themeutils\\..*", //
- "com\\.vaadin\\.tests\\..*", // exclude automated tests
- "com\\.vaadin\\.tools\\..*", //
- "com\\.vaadin\\.ui\\.themes\\..*", //
- // exact class level filtering
- "com\\.vaadin\\.event\\.FieldEvents", //
- "com\\.vaadin\\.event\\.LayoutEvents", //
- "com\\.vaadin\\.event\\.MouseEvents", //
- "com\\.vaadin\\.event\\.UIEvents", //
- "com\\.vaadin\\.server\\.VaadinPortlet", //
- "com\\.vaadin\\.server\\.MockServletConfig", //
- "com\\.vaadin\\.server\\.MockServletContext", //
- "com\\.vaadin\\.server\\.Constants", //
- "com\\.vaadin\\.server\\.VaadinServiceClassLoaderUtil", //
- "com\\.vaadin\\.server\\.VaadinServiceClassLoaderUtil\\$GetClassLoaderPrivilegedAction", //
- "com\\.vaadin\\.server\\.communication\\.FileUploadHandler\\$SimpleMultiPartInputStream", //
- "com\\.vaadin\\.server\\.communication\\.PushRequestHandler.*",
- "com\\.vaadin\\.server\\.communication\\.PushHandler.*", // PushHandler
- "com\\.vaadin\\.server\\.communication\\.DateSerializer", //
- "com\\.vaadin\\.server\\.communication\\.JSONSerializer", //
- // and its inner classes do not need to be serializable
- "com\\.vaadin\\.util\\.SerializerHelper", // fully static
- // class level filtering, also affecting nested classes and
- // interfaces
- "com\\.vaadin\\.server\\.LegacyCommunicationManager.*", //
- "com\\.vaadin\\.buildhelpers.*", //
- "com\\.vaadin\\.util\\.EncodeUtil.*", //
- "com\\.vaadin\\.util\\.ReflectTools.*", //
- "com\\.vaadin\\.data\\.provider\\.InMemoryDataProviderHelpers",
- "com\\.vaadin\\.data\\.provider\\.HierarchyMapper\\$TreeLevelQuery",
- "com\\.vaadin\\.data\\.util\\.ReflectTools.*", //
- "com\\.vaadin\\.data\\.util\\.JsonUtil.*", //
- "com\\.vaadin\\.data\\.util.BeanItemContainerGenerator.*",
- "com\\.vaadin\\.data\\.util\\.sqlcontainer\\.connection\\.MockInitialContextFactory",
- "com\\.vaadin\\.data\\.util\\.sqlcontainer\\.DataGenerator",
- "com\\.vaadin\\.data\\.util\\.sqlcontainer\\.FreeformQueryUtil",
- // the JSR-303 constraint interpolation context
- "com\\.vaadin\\.data\\.validator\\.BeanValidator\\$1", //
- "com\\.vaadin\\.sass.*", //
- "com\\.vaadin\\.testbench.*", //
- "com\\.vaadin\\.util\\.CurrentInstance\\$1", //
- "com\\.vaadin\\.server\\.AbstractClientConnector\\$1", //
- "com\\.vaadin\\.server\\.AbstractClientConnector\\$1\\$1", //
- "com\\.vaadin\\.server\\.JsonCodec\\$1", //
- "com\\.vaadin\\.server\\.communication\\.PushConnection", //
- "com\\.vaadin\\.server\\.communication\\.AtmospherePushConnection.*", //
- "com\\.vaadin\\.ui\\.components\\.colorpicker\\.ColorUtil", //
- "com\\.vaadin\\.util\\.ConnectorHelper", //
- "com\\.vaadin\\.server\\.VaadinSession\\$FutureAccess", //
- "com\\.vaadin\\.external\\..*", //
- "com\\.vaadin\\.util\\.WeakValueMap.*", //
- "com\\.vaadin\\.themes\\.valoutil\\.BodyStyleName", //
- "com\\.vaadin\\.server\\.communication\\.JSR356WebsocketInitializer.*", //
- "com\\.vaadin\\.screenshotbrowser\\.ScreenshotBrowser.*", //
- "com\\.vaadin\\.osgi.*", //
- "com\\.vaadin\\.server\\.osgi.*" };
-
- /**
- * Tests that all the relevant classes and interfaces under
- * {@link #BASE_PACKAGES} implement Serializable.
- *
- * @throws Exception
- */
- @Test
- public void testClassesSerializable() throws Exception {
- List<String> rawClasspathEntries = getRawClasspathEntries();
-
- List<String> classes = new ArrayList<>();
- for (String location : rawClasspathEntries) {
- classes.addAll(findServerClasses(location));
- }
-
- ArrayList<Field> nonSerializableFunctionFields = new ArrayList<>();
-
- List<Class<?>> nonSerializableClasses = new ArrayList<>();
- for (String className : classes) {
- Class<?> cls = Class.forName(className);
- // Don't add classes that have a @Ignore annotation on the class
- if (isTestClass(cls)) {
- continue;
- }
-
- // report fields that use lambda types that won't be serializable
- // (also in synthetic classes)
- Stream.of(cls.getDeclaredFields())
- .filter(field -> isFunctionalType(field.getGenericType()))
- .forEach(nonSerializableFunctionFields::add);
-
- // skip annotations and synthetic classes
- if (cls.isAnnotation() || cls.isSynthetic()) {
- continue;
- }
-
- if (Component.class.isAssignableFrom(cls) && !cls.isInterface()
- && !Modifier.isAbstract(cls.getModifiers())) {
- serializeAndDeserialize(cls);
- }
-
- // report non-serializable classes and interfaces
- if (!Serializable.class.isAssignableFrom(cls)) {
- if (cls.getSuperclass() == Object.class
- && cls.getInterfaces().length == 1) {
- // Single interface implementors
- Class<?> iface = cls.getInterfaces()[0];
-
- if (iface == Runnable.class) {
- // Ignore Runnables used with access()
- continue;
- } else if (iface == Comparator.class) {
- // Ignore inline comparators
- continue;
- }
- }
- nonSerializableClasses.add(cls);
- // TODO easier to read when testing
- // System.err.println(cls);
- }
- }
-
- // useful failure message including all non-serializable classes and
- // interfaces
- if (!nonSerializableClasses.isEmpty()) {
- failSerializableClasses(nonSerializableClasses);
- }
-
- if (!nonSerializableFunctionFields.isEmpty()) {
- failSerializableFields(nonSerializableFunctionFields);
- }
- }
-
- private void serializeAndDeserialize(Class<?> clazz)
- throws IOException, ClassNotFoundException, InstantiationException,
- IllegalAccessException, IllegalArgumentException,
- InvocationTargetException {
- Optional<Constructor<?>> defaultCtor = Stream
- .of(clazz.getDeclaredConstructors())
- .filter(ctor -> ctor.getParameterCount() == 0).findFirst();
- if (!defaultCtor.isPresent()) {
- return;
- }
- defaultCtor.get().setAccessible(true);
- Object instance = defaultCtor.get().newInstance();
- serializeAndDeserialize(instance);
- }
-
- public static <T> T serializeAndDeserialize(T instance)
- throws IOException, ClassNotFoundException {
- ByteArrayOutputStream bs = new ByteArrayOutputStream();
- ObjectOutputStream out = new ObjectOutputStream(bs);
- out.writeObject(instance);
- byte[] data = bs.toByteArray();
- ObjectInputStream in = new ObjectInputStream(
- new ByteArrayInputStream(data));
-
- @SuppressWarnings("unchecked")
- T readObject = (T) in.readObject();
-
- return readObject;
- }
-
- private void failSerializableFields(
- List<Field> nonSerializableFunctionFields) {
- String nonSerializableString = nonSerializableFunctionFields.stream()
- .map(field -> String.format("%s.%s",
- field.getDeclaringClass().getName(), field.getName()))
- .collect(Collectors.joining(", "));
-
- fail("Fields with functional types that are not serializable: "
- + nonSerializableString);
- }
-
- private void failSerializableClasses(
- List<Class<?>> nonSerializableClasses) {
- String nonSerializableString = "";
- for (Class<?> c : nonSerializableClasses) {
- nonSerializableString += ", " + c.getName();
- if (c.isAnonymousClass()) {
- nonSerializableString += "(super: ";
- nonSerializableString += c.getSuperclass().getName();
- nonSerializableString += ", interfaces: ";
- for (Class<?> i : c.getInterfaces()) {
- nonSerializableString += i.getName();
- nonSerializableString += ",";
- }
- nonSerializableString += ")";
- }
- }
- fail("Serializable not implemented by the following classes and interfaces: "
- + nonSerializableString);
-
- }
-
- private static boolean isFunctionalType(Type type) {
- return type.getTypeName().contains("java.util.function");
- }
-
- private boolean isTestClass(Class<?> cls) {
- if (cls.getEnclosingClass() != null
- && isTestClass(cls.getEnclosingClass())) {
- return true;
- }
-
- // Test classes with a @Test annotation on some method
- for (Method method : cls.getMethods()) {
- if (method.isAnnotationPresent(Test.class)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Lists all class path entries by splitting the class path string.
- *
- * Adapted from ClassPathExplorer.getRawClasspathEntries(), but without
- * filtering.
- *
- * @return List of class path segment strings
- */
- private static final List<String> getRawClasspathEntries() {
- // try to keep the order of the classpath
- List<String> locations = new ArrayList<>();
-
- String pathSep = System.getProperty("path.separator");
- String classpath = System.getProperty("java.class.path");
-
- if (classpath.startsWith("\"")) {
- classpath = classpath.substring(1);
- }
- if (classpath.endsWith("\"")) {
- classpath = classpath.substring(0, classpath.length() - 1);
- }
-
- String[] split = classpath.split(pathSep);
- locations.addAll(Arrays.asList(split));
-
- return locations;
- }
-
- /**
- * Finds the server side classes/interfaces under a class path entry -
- * either a directory or a JAR that matches {@link #JAR_PATTERN}.
- *
- * Only classes under {@link #BASE_PACKAGES} are considered, and those
- * matching {@link #EXCLUDED_PATTERNS} are filtered out.
- *
- * @param classpathEntry
- * @return
- * @throws IOException
- */
- private List<String> findServerClasses(String classpathEntry)
- throws IOException {
- Collection<String> classes = new ArrayList<>();
-
- File file = new File(classpathEntry);
- if (file.isDirectory()) {
- classes = findClassesInDirectory(null, file);
- } else if (file.getName().matches(JAR_PATTERN)) {
- classes = findClassesInJar(file);
- } else {
- System.out.println("Ignoring " + classpathEntry);
- return Collections.emptyList();
- }
-
- List<String> filteredClasses = new ArrayList<>();
- for (String className : classes) {
- boolean ok = false;
- for (String basePackage : BASE_PACKAGES) {
- if (className.startsWith(basePackage + ".")) {
- ok = true;
- break;
- }
- }
- for (String excludedPrefix : EXCLUDED_PATTERNS) {
- if (className.matches(excludedPrefix)) {
- ok = false;
- break;
- }
- }
-
- // Don't add test classes
- if (className.contains("Test")) {
- ok = false;
- }
-
- if (ok) {
- filteredClasses.add(className);
- }
- }
-
- return filteredClasses;
- }
-
- /**
- * Lists class names (based on .class files) in a JAR file.
- *
- * @param file
- * a valid JAR file
- * @return collection of fully qualified class names in the JAR
- * @throws IOException
- */
- private Collection<String> findClassesInJar(File file) throws IOException {
- Collection<String> classes = new ArrayList<>();
-
- try (JarFile jar = new JarFile(file)) {
- Enumeration<JarEntry> e = jar.entries();
- while (e.hasMoreElements()) {
- JarEntry entry = e.nextElement();
- if (entry.getName().endsWith(".class")) {
- String nameWithoutExtension = entry.getName()
- .replaceAll("\\.class", "");
- String className = nameWithoutExtension.replace('/', '.');
- classes.add(className);
- }
- }
- }
- return classes;
- }
-
- /**
- * Lists class names (based on .class files) in a directory (a package path
- * root).
- *
- * @param parentPackage
- * parent package name or null at root of hierarchy, used by
- * recursion
- * @param parent
- * File representing the directory to scan
- * @return collection of fully qualified class names in the directory
- */
- private static final Collection<String> findClassesInDirectory(
- String parentPackage, File parent) {
- if (parent.isHidden()
- || parent.getPath().contains(File.separator + ".")) {
- return Collections.emptyList();
- }
-
- if (parentPackage == null) {
- parentPackage = "";
- } else {
- parentPackage += ".";
- }
-
- Collection<String> classNames = new ArrayList<>();
-
- // add all directories recursively
- File[] files = parent.listFiles();
- for (File child : files) {
- if (child.isDirectory()) {
- classNames.addAll(findClassesInDirectory(
- parentPackage + child.getName(), child));
- } else if (child.getName().endsWith(".class")) {
- classNames.add(parentPackage.replace(File.separatorChar, '.')
- + child.getName().replaceAll("\\.class", ""));
- }
- }
-
- return classNames;
- }
-
- }
|