/*
* Copyright (C) 2013 salesforce.com, inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.auraframework.util;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Set;
import org.auraframework.util.ServiceLocator.ServiceLocatorException;
import org.reflections.ReflectionUtils;
import org.reflections.Reflections;
import org.reflections.scanners.MethodAnnotationsScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.reflections.scanners.TypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Sets;
/**
* @since 0.0.233
*/
public class ServiceLoaderImpl implements ServiceLoader {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AuraConfiguration {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Impl {
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PrimaryImpl {
}
private static final ServiceLoader instance = new ServiceLoaderImpl();
@SuppressWarnings("unchecked")
private static final Predicate<? super Method> predicate = Predicates.and(
ReflectionUtils.withModifier(Modifier.PUBLIC), ReflectionUtils.withAnnotation(Impl.class),
ReflectionUtils.withModifier(Modifier.STATIC), ReflectionUtils.withParametersCount(0));
private final Reflections reflections;
private ServiceLoaderImpl() {
Predicate<String> filter = new FilterBuilder().include(FilterBuilder.prefix("configuration"));
reflections = new Reflections(new ConfigurationBuilder().filterInputsBy(filter)
.setUrls(ClasspathHelper.forPackage("configuration"))
.setScanners(new TypeAnnotationsScanner(), new MethodAnnotationsScanner(), new TypesScanner()));
}
public static final ServiceLoader get() {
return instance;
}
@Override
public <T> T get(Class<T> type) {
try {
Set<Class<?>> classes = reflections.getTypesAnnotatedWith(AuraConfiguration.class);
// First try those marked with primary
T ret = get(type, classes, true, predicate);
if (ret != null) {
return ret;
}
return get(type, classes, false, predicate);
} catch (Throwable t) {
throw new ServiceLocatorException(t);
}
}
@SuppressWarnings("unchecked")
private <T> T get(Class<T> type, Set<Class<?>> classes, boolean primary, Predicate<? super Method> predicate) {
Set<Method> beanMethods = Sets.newHashSet();
Predicate<Method> pred;
pred = Predicates.and(predicate, ReflectionUtils.withReturnTypeAssignableTo(type));
if (primary) {
pred = Predicates.and(pred, ReflectionUtils.withAnnotation(PrimaryImpl.class));
}
/*
* This is a better way to do it, but hits a runtime dep on Guava 12, so
* until we upgrade to Guava 12, working around this.
*/
for (Class<?> clazz : classes) {
for (Method meth : clazz.getDeclaredMethods()) {
if (pred.apply(meth)) {
beanMethods.add(meth);
}
}
}
T ret = null;
try {
for (Method meth : beanMethods) {
T tmp = (T) meth.invoke(null);
if (tmp != null) {
if (ret != null) {
throw new ServiceLocatorException("More than one implementation found (primary=" + primary
+ ").");
}
ret = tmp;
}
}
} catch (Exception e) {
throw new ServiceLocatorException(e);
}
return ret;
}
@SuppressWarnings("unchecked")
@Override
public <T> Set<T> getAll(Class<T> type) {
Set<Method> beanMethods = Sets.newHashSet();
Set<T> ret = Sets.newHashSet();
Predicate<Method> pred = Predicates.and(predicate, ReflectionUtils.withReturnTypeAssignableTo(type));
Set<Class<?>> classes = reflections.getTypesAnnotatedWith(AuraConfiguration.class);
/*
* This is a better way to do it, but hits a runtime dep on Guava 12, so
* until we upgrade to Guava 12, working around this.
*/
for (Class<?> clazz : classes) {
for (Method meth : clazz.getDeclaredMethods()) {
if (pred.apply(meth)) {
beanMethods.add(meth);
}
}
}
try {
for (Method meth : beanMethods) {
T val = (T) meth.invoke(null);
if (val != null) {
ret.add(val);
}
}
} catch (Exception e) {
throw new ServiceLocatorException(e);
}
return ret;
}
@Override
public <T> T get(Class<T> type, final String name) {
try {
Predicate<? super Method> predicate = Predicates.and(ServiceLoaderImpl.predicate, new Predicate<Method>() {
@Override
public boolean apply(Method input) {
return input.getAnnotation(Impl.class).name().equals(name);
}
});
Set<Class<?>> classes = reflections.getTypesAnnotatedWith(AuraConfiguration.class);
// First try those marked with primary
T ret = get(type, classes, true, predicate);
if (ret != null) {
return ret;
}
return get(type, classes, false, predicate);
} catch (Throwable t) {
throw new ServiceLocatorException(t);
}
}
}