package de.rwth.idsg.bikeman.ixsi.dispatcher; import com.google.common.base.Preconditions; import de.rwth.idsg.bikeman.ixsi.IxsiProcessingException; import de.rwth.idsg.bikeman.ixsi.processor.api.ClassAwareProcessor; import de.rwth.idsg.bikeman.ixsi.processor.api.Processor; import de.rwth.idsg.bikeman.ixsi.processor.api.StaticRequestProcessor; import de.rwth.idsg.bikeman.ixsi.processor.api.SubscriptionRequestMessageProcessor; import de.rwth.idsg.bikeman.ixsi.processor.api.SubscriptionRequestProcessor; import de.rwth.idsg.bikeman.ixsi.processor.api.UserRequestProcessor; import de.rwth.idsg.ixsi.jaxb.RequestMessageGroup; import de.rwth.idsg.ixsi.jaxb.StaticDataRequestGroup; import de.rwth.idsg.ixsi.jaxb.SubscriptionRequestGroup; import de.rwth.idsg.ixsi.jaxb.UserTriggeredRequestChoice; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * @author Sevket Goekay <goekay@dbis.rwth-aachen.de> * @since 24.02.2016 */ @Slf4j @Component public class ProcessorProviderImpl implements ProcessorProvider { @Autowired private ListableBeanFactory beanFactory; private final LookupMap<StaticDataRequestGroup, StaticRequestProcessor> queryStaticMap = new LookupMap<>("queryStaticMap"); private final LookupMap<UserTriggeredRequestChoice, UserRequestProcessor> queryUserMap = new LookupMap<>("queryUserMap"); private final LookupMap<SubscriptionRequestGroup, SubscriptionRequestProcessor> subscriptionRequestMap = new LookupMap<>("subscriptionRequestMap"); private final LookupMap<RequestMessageGroup, SubscriptionRequestMessageProcessor> subscriptionRequestMessageMap = new LookupMap<>("subscriptionRequestMessageMap"); @PostConstruct public void init() { Set<ClassAwareProcessor> allProcessorBeans = getProcessorBeans(); queryStaticMap.putAll(getForProcessingClass(allProcessorBeans, StaticDataRequestGroup.class)); queryUserMap.putAll(getForProcessingClass(allProcessorBeans, UserTriggeredRequestChoice.class)); subscriptionRequestMap.putAll(getForProcessingClass(allProcessorBeans, SubscriptionRequestGroup.class)); subscriptionRequestMessageMap.putAll(getForProcessingClass(allProcessorBeans, RequestMessageGroup.class)); log.trace("Ready"); } @Override public StaticRequestProcessor find(StaticDataRequestGroup s) { return queryStaticMap.get(s.getClass()); } @Override public UserRequestProcessor find(UserTriggeredRequestChoice s) { return queryUserMap.get(s.getClass()); } @Override public SubscriptionRequestProcessor find(SubscriptionRequestGroup s) { return subscriptionRequestMap.get(s.getClass()); } @Override public SubscriptionRequestMessageProcessor find(RequestMessageGroup s) { return subscriptionRequestMessageMap.get(s.getClass()); } /** * Some pure Java black magic right here. Through the ListableBeanFactory we can discover all bean instances of * the given class type. Since we have many, many processors for request/message groups it was becoming * maintenance hell to manually inject and add them to the corresponding lookup map. */ private Set<ClassAwareProcessor> getProcessorBeans() { Map<String, Processor> beans = beanFactory.getBeansOfType(Processor.class); Collection<Processor> beanCollection = beans.values(); HashSet<ClassAwareProcessor> actual = new HashSet<>(beanCollection.size()); for (Processor bean : beanCollection) { actual.add((ClassAwareProcessor) bean); } return actual; } /** * Iterate through the ClassAwareProcessor set, and return only the processors that are able to process * objects that extend/implement the given base class. */ @SuppressWarnings("unchecked") private <T, K extends ClassAwareProcessor> List<K> getForProcessingClass(Set<ClassAwareProcessor> input, Class<T> baseMessageClazz) { return input.stream() .filter(p -> baseMessageClazz.isAssignableFrom(p.getProcessingClass())) .map(p -> (K) p) .collect(Collectors.toList()); } /** * A simple wrapper around HashMap with minor modifications for our own use case. Since we have many, many * processors for request/message groups, it was a maintenance hell to manually set the class type of each * processor in the map. With the addition of ClassAwareProcessor, each processor can now provide this class * information. */ @RequiredArgsConstructor private static class LookupMap<T, K extends ClassAwareProcessor> { private final String name; private final HashMap<Class<? extends T>, K> lookup = new HashMap<>(); private K get(Class<? extends T> clazz) { K p = lookup.get(clazz); if (p == null) { throw new IxsiProcessingException( "No processor is registered for the incoming request of type: " + clazz); } else { return p; } } @SuppressWarnings("unchecked") private void putAll(List<K> items) { for (K item : items) { lookup.put(Preconditions.checkNotNull(item.getProcessingClass()), item); } log(); } private void log() { log.info("{} (size:{}) => {}", name, lookup.size(), lookup); } } }