ImplementationFinder.java

package fr.sii.ogham.testing.assertion.internal.helper;

import static org.apache.commons.lang3.reflect.FieldUtils.readField;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import fr.sii.ogham.core.message.Message;
import fr.sii.ogham.core.sender.ConditionalSender;
import fr.sii.ogham.core.sender.ContentTranslatorSender;
import fr.sii.ogham.core.sender.MessageSender;
import fr.sii.ogham.core.sender.MultiImplementationSender;
import fr.sii.ogham.core.sender.MultiImplementationSender.Implementation;
import fr.sii.ogham.core.service.CleanableMessagingService;
import fr.sii.ogham.core.service.EverySupportingMessagingService;
import fr.sii.ogham.core.service.MessagingService;
import fr.sii.ogham.core.service.WrapExceptionMessagingService;
import fr.sii.ogham.core.template.parser.AutoDetectTemplateParser;
import fr.sii.ogham.core.template.parser.TemplateParser;
import fr.sii.ogham.core.template.parser.AutoDetectTemplateParser.TemplateImplementation;
import fr.sii.ogham.core.translator.content.ContentTranslator;
import fr.sii.ogham.core.translator.content.EveryContentTranslator;
import fr.sii.ogham.core.translator.content.MultiContentTranslator;
import fr.sii.ogham.core.translator.content.TemplateContentTranslator;
import fr.sii.ogham.email.message.Email;
import fr.sii.ogham.email.sender.EmailSender;
import fr.sii.ogham.sms.message.Sms;
import fr.sii.ogham.sms.sender.SmsSender;

/**
 * Utility class to find implementations used by Ogham service.
 * 
 * @author Aurélien Baudet
 *
 */
public final class ImplementationFinder {
	private static final String DELEGATE_FIELD = "delegate";

	/**
	 * Find recursively a finder of the provided class
	 * 
	 * @param <T>
	 *            the type of the found sender
	 * @param service
	 *            the messaging service
	 * @param clazz
	 *            the class of the sender to find
	 * @return the found sender
	 */
	public static <T extends MessageSender> T findSender(MessagingService service, Class<T> clazz) {
		Set<T> found = findSenders(service, clazz);
		if (found.isEmpty()) {
			throw new IllegalStateException("Failed to find MessageSender of " + clazz.getTypeName());
		}
		if (found.size() == 1) {
			return found.iterator().next();
		}
		throw new IllegalStateException("Several matching MessageSender for " + clazz.getTypeName() + " found");
	}

	/**
	 * Find all senders for the given type
	 * 
	 * @param <T>
	 *            the type of the senders to find
	 * @param service
	 *            the messaging service
	 * @param clazz
	 *            the class of the senders to find
	 * @return the found senders
	 */
	@SuppressWarnings("unchecked")
	public static <T extends MessageSender> Set<T> findSenders(MessagingService service, Class<T> clazz) {
		try {
			Set<T> found = new HashSet<>();
			List<ConditionalSender> senders = (List<ConditionalSender>) readField(getRealService(service), "senders", true);
			for (ConditionalSender sender : senders) {
				found.addAll(findSenders(sender, clazz));
			}
			return found;
		} catch (IllegalAccessException e) {
			throw new IllegalStateException("Failed to find senders of type " + clazz.getTypeName(), e);
		}
	}

	/**
	 * Find template parsers of the given type.
	 * 
	 * @param <T>
	 *            the type of the template parsers to find
	 * @param service
	 *            the messaging service
	 * @param clazz
	 *            the class of the template parsers to find
	 * @return the found parsers
	 */
	public static <T extends TemplateParser> Set<FoundParser<T>> findParsers(MessagingService service, Class<T> clazz) {
		try {
			Set<FoundParser<T>> found = new HashSet<>();
			Set<ContentTranslatorSender> translatorSenders = findSenders(service, ContentTranslatorSender.class);
			for (ContentTranslatorSender sender : translatorSenders) {
				Set<TemplateContentTranslator> translators = findTranslators(sender, TemplateContentTranslator.class);
				for (TemplateContentTranslator translator : translators) {
					found.addAll(findParsers(clazz, translator, (MessageSender) readField(sender, DELEGATE_FIELD, true)));
				}
			}
			return found;
		} catch (IllegalAccessException e) {
			throw new IllegalStateException("Failed to find parser of type " + clazz.getTypeName(), e);
		}
	}

	private static MessagingService getRealService(MessagingService service) {
		try {
			if (service instanceof WrapExceptionMessagingService) {
				return getRealService((MessagingService) readField(service, DELEGATE_FIELD, true));
			}
			if (service instanceof CleanableMessagingService) {
				return getRealService((MessagingService) readField(service, DELEGATE_FIELD, true));
			}
			if (service instanceof EverySupportingMessagingService) {
				return service;
			}
			throw new IllegalStateException("Unknown MessagingService implementation, please add it here");
		} catch (IllegalAccessException e) {
			throw new IllegalStateException("Failed to find real MessagingService", e);
		}
	}

	@SuppressWarnings("unchecked")
	private static <T extends MessageSender> Set<T> findSenders(MessageSender sender, Class<T> clazz) {
		try {
			Set<T> found = new HashSet<>();
			if (clazz.isAssignableFrom(sender.getClass())) {
				found.add((T) sender);
			}
			// Any sender that delegates in the chain (FillerSender,
			// AttachmentResourceTranslatorSender, ContentTranslatorSender,
			// PhoneNumberTranslatorSender)
			// TODO: FallbackSender
			if (delegates(sender)) {
				MessageSender delegate = (MessageSender) readField(sender, DELEGATE_FIELD, true);
				found.addAll(findSenders(delegate, clazz));
			}
			if (sender instanceof MultiImplementationSender<?>) {
				found.addAll(findSenders((MultiImplementationSender<?>) sender, clazz));
			}
			return found;
		} catch (IllegalAccessException e) {
			throw new IllegalStateException("Failed to find senders of type " + clazz.getTypeName(), e);
		}
	}

	private static boolean delegates(MessageSender sender) {
		try {
			Object value = readField(sender, DELEGATE_FIELD, true);
			return value instanceof MessageSender;
		} catch (IllegalAccessException | IllegalArgumentException e) { // NOSONAR
			return false;
		}
	}

	@SuppressWarnings("unchecked")
	private static <T extends MessageSender> Set<T> findSenders(MultiImplementationSender<?> sender, Class<T> clazz) {
		Set<T> found = new HashSet<>();
		List<Implementation> implementations = sender.getImplementations();
		for (Implementation impl : implementations) {
			if (clazz.isAssignableFrom(impl.getSender().getClass())) {
				found.add((T) impl.getSender());
			}
		}
		return found;
	}

	@SuppressWarnings("unchecked")
	private static <T extends TemplateParser> Set<FoundParser<T>> findParsers(Class<T> clazz, TemplateContentTranslator translator, MessageSender sender) throws IllegalAccessException {
		Set<FoundParser<T>> found = new HashSet<>();
		TemplateParser parser = (TemplateParser) readField(translator, "parser", true);
		if (clazz.isAssignableFrom(parser.getClass())) {
			found.add(new FoundParser<>((T) parser, getMessageType(sender)));
		}
		if (parser instanceof AutoDetectTemplateParser) {
			found.addAll(findParsers(clazz, (AutoDetectTemplateParser) parser, sender));
		}
		return found;
	}

	@SuppressWarnings("unchecked")
	private static <T extends TemplateParser> Set<FoundParser<T>> findParsers(Class<T> clazz, AutoDetectTemplateParser parser, MessageSender sender) throws IllegalAccessException {
		Set<FoundParser<T>> found = new HashSet<>();
		List<TemplateImplementation> implementations = (List<TemplateImplementation>) readField(parser, "implementations", true);
		for (TemplateImplementation impl : implementations) {
			if (clazz.isAssignableFrom(impl.getParser().getClass())) {
				found.add(new FoundParser<>((T) impl.getParser(), getMessageType(sender)));
			}
		}
		return found;
	}

	private static Class<? extends Message> getMessageType(MessageSender sender) {
		Set<EmailSender> emailSenders = findSenders(sender, EmailSender.class);
		if (!emailSenders.isEmpty()) {
			return Email.class;
		}
		Set<SmsSender> smsSenders = findSenders(sender, SmsSender.class);
		if (!smsSenders.isEmpty()) {
			return Sms.class;
		}
		throw new IllegalStateException("Failed to find message type");
	}

	private static <T extends ContentTranslator> Set<T> findTranslators(ContentTranslatorSender translatorSender, Class<T> clazz) {
		try {
			ContentTranslator translator = (ContentTranslator) readField(translatorSender, "translator", true);
			return findTranslators(translator, clazz);
		} catch (IllegalAccessException e) {
			throw new IllegalStateException("Failed to find translator of type " + clazz.getTypeName(), e);
		}
	}

	private static <T extends ContentTranslator> Set<T> findTranslators(ContentTranslator translator, Class<T> clazz) {
		try {
			Set<T> found = new HashSet<>();
			if (translator instanceof EveryContentTranslator) {
				found.addAll(findTranslators((EveryContentTranslator) translator, clazz));
			}
			if (translator instanceof MultiContentTranslator) {
				found.addAll(findTranslators((ContentTranslator) readField(translator, DELEGATE_FIELD, true), clazz));
			}
			return found;
		} catch (IllegalAccessException e) {
			throw new IllegalStateException("Failed to read 'delegate' of MultiContentTranslator", e);
		}
	}

	@SuppressWarnings("unchecked")
	private static <T extends ContentTranslator> Set<T> findTranslators(EveryContentTranslator translator, Class<T> clazz) {
		Set<T> found = new HashSet<>();
		for (ContentTranslator t : translator.getTranslators()) {
			if (clazz.isAssignableFrom(t.getClass())) {
				found.add((T) t);
			}
		}
		return found;
	}

	private ImplementationFinder() {
		super();
	}
}