/*
* $#
* FOS Server
*
* Copyright (C) 2013 Feedzai SA
*
* This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
* Lesser General Public License version 3 (the "GPL License"). You may choose either license to govern
* your use of this software only upon the condition that you accept all of the terms of either the Apache
* License or the LGPL License.
*
* You may obtain a copy of the Apache License and the LGPL License at:
*
* http://www.apache.org/licenses/LICENSE-2.0.txt
* http://www.gnu.org/licenses/lgpl-3.0.txt
*
* Unless required by applicable law or agreed to in writing, software distributed under the Apache License
* or the LGPL License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the Apache License and the LGPL License for the specific language governing
* permissions and limitations under the Apache License and the LGPL License.
* #$
*/
package com.feedzai.fos.server.remote.impl;
import com.feedzai.fos.server.remote.api.RemoteInterface;
import org.apache.commons.collections15.comparators.ComparatorChain;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.*;
import static org.junit.Assert.*;
/**
* This test is a little unorthodox. It tests that Remote exception match with their targeted implementation.
* It does so by checking {@link com.feedzai.fos.server.remote.api.RemoteInterface} .
*
* @author diogo.ferreira
*/
public class RemoteInterfacesTest {
public RemoteInterfacesTest() {
}
@Test
public void testRemoteInterfaces() throws Exception {
for (Class klass : getClasses("com.feedzai.fos.server.remote.impl")) {
if (klass.isAnnotationPresent(RemoteInterface.class)) {
RemoteInterface implementation = (RemoteInterface) klass.getAnnotation(RemoteInterface.class);
Class<?> matchingClass = implementation.of();
testMatch(matchingClass, klass, 0);
}
}
}
private void testMatch(Class<?> api, Class<?> remote, int remoteFieldAdditions) {
Method[] apiMethods = api.getMethods();
Method[] remoteMethods = remote.getMethods();
Arrays.sort(apiMethods, buildComparator());
Arrays.sort(remoteMethods, buildComparator());
// Checks if @RemoteInterface is different from the class/interface itself.
assertNotSame(String.format("@RemoteInterface cannot be the same for the class name in %s", api.getSimpleName()), api, remote);
// Checks if the remote implementation can be assigned to Remote (i.e. if in the hierarchy extends/implements Remote).
assertTrue(String.format("'%s' does not implement '%s'", remote.getSimpleName(), Remote.class.getSimpleName()), Remote.class.isAssignableFrom(remote));
assertEquals(desc(api, null, "Number of methods matches"), apiMethods.length, remoteMethods.length);
for (int i = 0; i < apiMethods.length; i++) {
// Check if the names match
Method expected = apiMethods[i];
Method actual = remoteMethods[i];
assertEquals(desc(api, actual, "Names match"), expected.getName(), actual.getName());
assertTrue(desc(api, actual, "Number of arguments matches"), expected.getParameterTypes().length - actual.getParameterAnnotations().length <= remoteFieldAdditions);
boolean remoteOrException = false;
for (Class klass : actual.getExceptionTypes()) {
remoteOrException = Exception.class.equals(klass) || RemoteException.class.equals(klass);
if (remoteOrException) {
break;
}
}
// Checks if remote implementations throw Exception or RemoteException.
assertTrue(desc(remote, actual, String.format("%s does not throw either RemoteException or Exception", actual.getName())), remoteOrException);
}
}
private String desc(Class klass, Method method, String desc) {
String methodName = method == null ? "none" : method.getName();
return String.format("[%s#%s] %s", klass.getSimpleName(), methodName, desc);
}
private Comparator<Method> buildComparator() {
ComparatorChain<Method> chain = new ComparatorChain<Method>();
chain.addComparator(new Comparator<Method>() {
@Override
public int compare(Method o1, Method o2) {
return o1.getName().compareTo(o2.getName());
}
});
chain.addComparator(new Comparator<Method>() {
@Override
public int compare(Method o1, Method o2) {
return o1.getParameterTypes().length - o2.getParameterTypes().length;
}
});
return chain;
}
/**
* What follows is a shameless adaptation of http://www.dzone.com/snippets/get-all-classes-within-package
*/
/**
* Scans all classes accessible from the context class loader which belong to the given package and subpackages.
*
* @param packageName The base package
* @return The classes
* @throws ClassNotFoundException
* @throws java.io.IOException
*/
private static Class[] getClasses(String packageName)
throws ClassNotFoundException, IOException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
assert classLoader != null;
String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);
List<File> dirs = new ArrayList<>();
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(new File(resource.getFile()));
}
ArrayList<Class> classes = new ArrayList<>();
for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}
return classes.toArray(new Class[classes.size()]);
}
/**
* Recursive method used to find all classes in a given directory and subdirs.
*
* @param directory The base directory
* @param packageName The package name for classes found inside the base directory
* @return The classes
* @throws ClassNotFoundException
*/
private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
List<Class> classes = new ArrayList<>();
if (!directory.exists()) {
return classes;
}
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
assert !file.getName().contains("");
classes.addAll(findClasses(file, packageName + "" + file.getName()));
} else if (file.getName().contains("Remote")) {
try {
classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
} catch (NullPointerException ex) {
// Some classes may fail their static initialization, we don't really care.
}
}
}
return classes;
}
}