mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Merge branch 'master' into feature/RM-2868_Create_Mark_Selection_Widget
This commit is contained in:
5
pom.xml
5
pom.xml
@@ -74,6 +74,11 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
<version>0.9.10</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
@@ -345,6 +345,11 @@
|
||||
</exclusions>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
|
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Records Management Module
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* -
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
* -
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* -
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
* -
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
package org.alfresco.module.org_alfresco_module_rm.api;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class CommunityPublicAPIUnitTest
|
||||
{
|
||||
@Test
|
||||
public void testPublicAPIConsistency()
|
||||
{
|
||||
HashMultimap<Class<?>, Class<?>> knownBadReferences = HashMultimap.create();
|
||||
PublicAPITestUtil.testPublicAPIConsistency("org.alfresco.module.org_alfresco_module_rm", knownBadReferences);
|
||||
}
|
||||
}
|
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Records Management Module
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* -
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
* -
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* -
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
* -
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
package org.alfresco.module.org_alfresco_module_rm.api;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.GenericArrayType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import org.alfresco.api.AlfrescoPublicApi;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.reflections.Reflections;
|
||||
|
||||
/**
|
||||
* A utility class to help testing the Alfresco public API.
|
||||
*
|
||||
* @author Tom Page
|
||||
* @since 2.5
|
||||
*/
|
||||
public class PublicAPITestUtil
|
||||
{
|
||||
private static final String ALFRESCO_PACKAGE = "org.alfresco";
|
||||
|
||||
/**
|
||||
* Check the consistency of the public API exposed from the given package. For each class in the package that is
|
||||
* annotated {@link AlfrescoPublicApi}, check that no exposed methods (or fields, constructors, etc.) use
|
||||
* non-public-API classes from Alfresco.
|
||||
*
|
||||
* @param basePackageName The package to check classes within.
|
||||
* @param knownBadReferences Any references that would cause this test to fail, but which we don't want to change.
|
||||
* The keys should be public API classes within our code and the values should be the non-public-API
|
||||
* class that is being referenced.
|
||||
*/
|
||||
public static void testPublicAPIConsistency(String basePackageName, SetMultimap<Class<?>, Class<?>> knownBadReferences)
|
||||
{
|
||||
Reflections reflections = new Reflections(basePackageName);
|
||||
Set<Class<?>> publicAPIClasses = reflections.getTypesAnnotatedWith(AlfrescoPublicApi.class, true);
|
||||
|
||||
SetMultimap<Class<?>, Class<?>> referencedFrom = HashMultimap.create();
|
||||
Set<Class<?>> referencedClasses = new HashSet<>();
|
||||
for (Class<?> publicAPIClass : publicAPIClasses)
|
||||
{
|
||||
Set<Class<?>> referencedClassesFromClass = getReferencedClassesFromClass(publicAPIClass, new HashSet<>());
|
||||
referencedClassesFromClass.forEach(clazz -> referencedFrom.put(clazz, publicAPIClass));
|
||||
|
||||
// Remove any references in knownBadReferences and error if an expected reference wasn't found.
|
||||
if (knownBadReferences.containsKey(publicAPIClass))
|
||||
{
|
||||
for (Class<?> clazz : knownBadReferences.get(publicAPIClass))
|
||||
{
|
||||
assertTrue("Supplied knownBadReferences expects " + clazz + " to be referenced by " + publicAPIClass
|
||||
+ ", but no such error was found", referencedClassesFromClass.remove(clazz));
|
||||
}
|
||||
}
|
||||
|
||||
referencedClasses.addAll(referencedClassesFromClass);
|
||||
}
|
||||
|
||||
List<String> errorMessages = new ArrayList<>();
|
||||
for (Class<?> referencedClass : referencedClasses)
|
||||
{
|
||||
if (isInAlfresco(referencedClass) && !isPartOfPublicApi(referencedClass))
|
||||
{
|
||||
Set<String> referencerNames = referencedFrom.get(referencedClass).stream().map(c -> c.getName())
|
||||
.collect(Collectors.toSet());
|
||||
errorMessages.add(referencedClass.getName() + " <- " + StringUtils.join(referencerNames, ", "));
|
||||
}
|
||||
}
|
||||
|
||||
if (!errorMessages.isEmpty())
|
||||
{
|
||||
System.out.println("Errors found:");
|
||||
System.out.println(StringUtils.join(errorMessages, "\n"));
|
||||
}
|
||||
|
||||
assertEquals("Found references to non-public API classes from public API classes.", Collections.emptyList(),
|
||||
errorMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given class is a part of the Alfresco public API.
|
||||
*
|
||||
* @param clazz The class to check.
|
||||
* @return {@code true} if the given class is annotated with {@link AlfrescoPublicApi}.
|
||||
*/
|
||||
private static boolean isPartOfPublicApi(Class<?> clazz)
|
||||
{
|
||||
if (clazz.getAnnotation(AlfrescoPublicApi.class) != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (clazz.getEnclosingClass() != null)
|
||||
{
|
||||
return isPartOfPublicApi(clazz.getEnclosingClass());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the classes referenced by the given class, which might be used by an extension. We consider visible
|
||||
* methods, constructors, fields and inner classes, as well as superclasses and interfaces extended by the class.
|
||||
*
|
||||
* @param initialClass The class to analyse.
|
||||
* @param consideredClasses Classes that have already been considered, and which should not be considered again. If
|
||||
* the given class has already been considered then an empty set will be returned. This set will be
|
||||
* updated with the given class.
|
||||
* @return The set of classes that might be accessible by an extension of this class.
|
||||
*/
|
||||
private static Set<Class<?>> getReferencedClassesFromClass(Class<?> initialClass, Set<Class<?>> consideredClasses)
|
||||
{
|
||||
Set<Class<?>> referencedClasses = new HashSet<>();
|
||||
|
||||
if (consideredClasses.add(initialClass))
|
||||
{
|
||||
for (Method method : initialClass.getDeclaredMethods())
|
||||
{
|
||||
if (isVisibleToExtender(method.getModifiers()))
|
||||
{
|
||||
referencedClasses.addAll(getClassesFromMethod(method));
|
||||
}
|
||||
}
|
||||
for (Constructor<?> constructor : initialClass.getDeclaredConstructors())
|
||||
{
|
||||
if (isVisibleToExtender(constructor.getModifiers()))
|
||||
{
|
||||
referencedClasses.addAll(getClassesFromConstructor(constructor));
|
||||
}
|
||||
}
|
||||
for (Field field : initialClass.getDeclaredFields())
|
||||
{
|
||||
if (isVisibleToExtender(field.getModifiers()))
|
||||
{
|
||||
referencedClasses.addAll(getClassesFromField(field));
|
||||
}
|
||||
}
|
||||
for (Class<?> clazz : initialClass.getDeclaredClasses())
|
||||
{
|
||||
if (isVisibleToExtender(clazz.getModifiers()))
|
||||
{
|
||||
referencedClasses.addAll(getReferencedClassesFromClass(clazz, consideredClasses));
|
||||
}
|
||||
}
|
||||
if (initialClass.getSuperclass() != null)
|
||||
{
|
||||
referencedClasses
|
||||
.addAll(getReferencedClassesFromClass(initialClass.getSuperclass(), consideredClasses));
|
||||
}
|
||||
for (Class<?> clazz : initialClass.getInterfaces())
|
||||
{
|
||||
referencedClasses.addAll(getReferencedClassesFromClass(clazz, consideredClasses));
|
||||
}
|
||||
}
|
||||
return referencedClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the supplied {@link Executable#getModifiers() modifiers} indicate that an extension can access the
|
||||
* element. Here we assume that an extension can see public and protected items, but not package protected (or
|
||||
* private).
|
||||
*
|
||||
* @param modifiers The java language modifiers.
|
||||
* @return {@code true} if the item is visible to an extension.
|
||||
*/
|
||||
private static boolean isVisibleToExtender(int modifiers)
|
||||
{
|
||||
return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all classes involved in the signature of the given method.
|
||||
*
|
||||
* @param method The method to analyse.
|
||||
* @return The set of classes.
|
||||
*/
|
||||
private static Set<Class<?>> getClassesFromMethod(Method method)
|
||||
{
|
||||
Set<Type> types = getTypesFromMethod(method);
|
||||
return getClassesFromTypes(types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all classes involved in the signature of the given constructor.
|
||||
*
|
||||
* @param constructor The constructor to analyse.
|
||||
* @return The set of classes.
|
||||
*/
|
||||
private static Set<Class<?>> getClassesFromConstructor(Constructor<?> constructor)
|
||||
{
|
||||
Set<Type> types = getTypesFromConstructor(constructor);
|
||||
return getClassesFromTypes(types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all classes involved in the type of the supplied field. For example {@code Pair<Set<String>, Integer> foo}
|
||||
* involves four classes.
|
||||
*
|
||||
* @param field The field to look at.
|
||||
* @return The set of classes.
|
||||
*/
|
||||
private static Set<Class<?>> getClassesFromField(Field field)
|
||||
{
|
||||
Set<Type> types = Sets.newHashSet(field.getGenericType());
|
||||
return getClassesFromTypes(types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all types references by the supplied method signature (i.e. the parameters, return type and exceptions).
|
||||
*
|
||||
* @param method The method to analyse.
|
||||
* @return The set of types.
|
||||
*/
|
||||
private static Set<Type> getTypesFromMethod(Method method)
|
||||
{
|
||||
Set<Type> methodTypes = new HashSet<>();
|
||||
methodTypes.addAll(Sets.newHashSet(method.getGenericParameterTypes()));
|
||||
methodTypes.add(method.getGenericReturnType());
|
||||
methodTypes.addAll(Sets.newHashSet(method.getGenericExceptionTypes()));
|
||||
return methodTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all types referenced by the supplied constructor (i.e. the parameters and exceptions).
|
||||
*
|
||||
* @param constructor The constructor to analyse.
|
||||
* @return The set of types.
|
||||
*/
|
||||
private static Set<Type> getTypesFromConstructor(Constructor<?> constructor)
|
||||
{
|
||||
Set<Type> methodTypes = new HashSet<>();
|
||||
methodTypes.addAll(Sets.newHashSet(constructor.getGenericParameterTypes()));
|
||||
methodTypes.addAll(Sets.newHashSet(constructor.getGenericExceptionTypes()));
|
||||
return methodTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all classes that are within the supplied types. For example a {@code Pair<Set<String>, Integer>} contains
|
||||
* references to four classes.
|
||||
*
|
||||
* @param methodTypes The set of types to examine.
|
||||
* @return The set of classes used to form the given types.
|
||||
*/
|
||||
private static Set<Class<?>> getClassesFromTypes(Set<Type> methodTypes)
|
||||
{
|
||||
Set<Class<?>> methodClasses = new HashSet<>();
|
||||
for (Type type : methodTypes)
|
||||
{
|
||||
methodClasses.addAll(getClassesFromType(type, new HashSet<>()));
|
||||
}
|
||||
return methodClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all classes that are within the supplied type. For example a {@code Pair<Set<String>, Integer>} contains
|
||||
* references to four classes.
|
||||
*
|
||||
* @param type The type to examine.
|
||||
* @param processedTypes The set of types which have already been processed. If {@code type} is within this set then
|
||||
* the method returns an empty set, to prevent analysis of the same type multiple times, and to guard
|
||||
* against circular references. The underlying set is updated with the given type.
|
||||
* @return The set of classes used to form the given type.
|
||||
*/
|
||||
private static Set<Class<?>> getClassesFromType(Type type, Set<Type> processedTypes)
|
||||
{
|
||||
Set<Class<?>> returnClasses = new HashSet<>();
|
||||
|
||||
if (processedTypes.add(type))
|
||||
{
|
||||
if (type instanceof ParameterizedType)
|
||||
{
|
||||
ParameterizedType parameterizedType = (ParameterizedType) type;
|
||||
returnClasses.add((Class<?>) parameterizedType.getRawType());
|
||||
|
||||
for (Type t : parameterizedType.getActualTypeArguments())
|
||||
{
|
||||
returnClasses.addAll(getClassesFromType(t, processedTypes));
|
||||
}
|
||||
}
|
||||
else if (type instanceof Class)
|
||||
{
|
||||
Class<?> clazz = (Class<?>) type;
|
||||
if (clazz.isArray())
|
||||
{
|
||||
returnClasses.add(clazz.getComponentType());
|
||||
}
|
||||
returnClasses.add(clazz);
|
||||
}
|
||||
else if (type instanceof WildcardType)
|
||||
{
|
||||
// No-op - Caller can choose what type to use.
|
||||
}
|
||||
else if (type instanceof TypeVariable<?>)
|
||||
{
|
||||
TypeVariable<?> typeVariable = (TypeVariable<?>) type;
|
||||
for (Type bound : typeVariable.getBounds())
|
||||
{
|
||||
returnClasses.addAll(getClassesFromType(bound, processedTypes));
|
||||
}
|
||||
}
|
||||
else if (type instanceof GenericArrayType)
|
||||
{
|
||||
GenericArrayType genericArrayType = (GenericArrayType) type;
|
||||
returnClasses.addAll(getClassesFromType(genericArrayType.getGenericComponentType(), processedTypes));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException("This test was not written to work with type " + type);
|
||||
}
|
||||
}
|
||||
return returnClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a class is within org.alfresco, and so whether it could potentially be part of the public API.
|
||||
*
|
||||
* @param type The class to check.
|
||||
* @return {@code true} if this is an Alfresco class.
|
||||
*/
|
||||
private static boolean isInAlfresco(Class<?> type)
|
||||
{
|
||||
if (type.getPackage() == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return type.getPackage().getName().startsWith(ALFRESCO_PACKAGE);
|
||||
}
|
||||
}
|
@@ -99,5 +99,6 @@ public class MockAuthenticationUtilHelper
|
||||
when(mockAuthenticationUtil.getFullyAuthenticatedUser()).thenReturn(fullyAuthenticatedUser);
|
||||
when(mockAuthenticationUtil.getRunAsUser()).thenReturn(fullyAuthenticatedUser);
|
||||
when(mockAuthenticationUtil.getSystemUserName()).thenReturn("system");
|
||||
when(mockAuthenticationUtil.getGuestUserName()).thenReturn("guest");
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user