/**
* Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG
* 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 com.sixt.service.framework.kafka.messaging;
import com.google.inject.ConfigurationException;
import com.google.inject.Injector;
import com.google.inject.ProvisionException;
import com.google.protobuf.Parser;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.validation.constraints.NotNull;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A TypeDictionary that finds MessageHandlers as well as Protobuf messages via reflection.
* <p>
* The scope of this TypeDictionary is global: all found messages and handlers from the classpath are put to this dictionary.
* <p>
* If you want to distinguish e.g. between handlers for the same message type but different topics, this is not the
* right TypeDictionary implementation.
*/
public final class ReflectionTypeDictionaryFactory {
private static final Logger logger = LoggerFactory.getLogger(ReflectionTypeDictionaryFactory.class);
private final Injector injector;
public ReflectionTypeDictionaryFactory(@NotNull Injector injector) {
this.injector = injector;
}
public TypeDictionary createFromClasspath() {
logger.info("Creating TypeDictionary using reflection from standard classpath.");
return new TypeDictionary(populateHandlersFromClasspath(), populateParsersFromClasspath());
}
public Map<MessageType, MessageHandler<? extends com.google.protobuf.Message>> populateHandlersFromClasspath() {
Map<MessageType, MessageHandler<? extends com.google.protobuf.Message>> handlers = new HashMap<>();
List<Class<? extends MessageHandler>> foundHandlers = new ArrayList<>();
new FastClasspathScanner()
.matchClassesImplementing(MessageHandler.class, matchingClass ->
foundHandlers.add(matchingClass)).scan();
foundHandlers.forEach((handlerClass) -> {
Type[] interfaces = handlerClass.getGenericInterfaces();
for (Type it : interfaces) {
if (it instanceof ParameterizedType) {
ParameterizedType pt = ((ParameterizedType) it);
if (pt.getRawType().getTypeName().equals((MessageHandler.class.getTypeName()))) {
// We expect exactly one type argument
Type t = pt.getActualTypeArguments()[0];
MessageType type = MessageType.of(t);
MessageHandler<? extends com.google.protobuf.Message> handler = null;
try {
// Ask Guice for an instance of the handler.
// We cannot simply use e.g. the default constructor as any meaningful handler would need to
// be wired to dependencies such as databases, metrics, etc.
handler = (MessageHandler<? extends com.google.protobuf.Message>) injector.getInstance(handlerClass);
} catch (ConfigurationException | ProvisionException e) {
logger.warn("Cannot instantiate MessageHandler {} using Guice.", handlerClass, e);
}
if (handler != null) {
MessageHandler previous = handlers.put(type, handler);
if (previous == null) {
logger.info("Added message handler {} for type {}", handlerClass, type);
} else {
logger.warn("Duplicate message handler {} for type {} was replaced by {}", previous.getClass().getTypeName(), type, handlerClass);
}
}
}
} else {
logger.warn("Cannot add untyped instance of MessageHander {} to TypeDictionary", handlerClass.getTypeName());
}
}
});
return handlers;
}
public Map<MessageType, Parser<com.google.protobuf.Message>> populateParsersFromClasspath() {
Map<MessageType, Parser<com.google.protobuf.Message>> parsers = new HashMap<>();
List<Class<? extends com.google.protobuf.GeneratedMessageV3>> foundProtoMessages = new ArrayList<>();
new FastClasspathScanner()
.matchSubclassesOf(com.google.protobuf.GeneratedMessageV3.class, matchingClass ->
foundProtoMessages.add(matchingClass)).scan();
// This algorithm adds parsers for all protobuf messages in the classpath including base types such as com.google.protobuf.DoubleValue.
for (Class<? extends com.google.protobuf.GeneratedMessageV3> clazz : foundProtoMessages) {
try {
java.lang.reflect.Method method = clazz.getMethod("parser"); // static method, no arguments
@SuppressWarnings("unchecked")
Parser<com.google.protobuf.Message> parser = (Parser<com.google.protobuf.Message>) method.invoke(null, (Object[]) null); // static method, no arguments
parsers.put(MessageType.of(clazz), parser);
// too noisy: logger.debug("Added parser for protobuf type {}", clazz.getTypeName());
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ignored) {
// too noisy: logger.debug("Ignoring protobuf type {} as we cannot invoke static method parse().", clazz.getTypeName());
}
}
return parsers;
}
}