SenderImplementationBuilderHelper.java
package fr.sii.ogham.core.builder.sender;
import static fr.sii.ogham.core.util.BuilderUtils.instantiateBuilder;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fr.sii.ogham.core.builder.ActivableAtRuntime;
import fr.sii.ogham.core.builder.Builder;
import fr.sii.ogham.core.builder.condition.RequiredClass;
import fr.sii.ogham.core.builder.condition.RequiredClasses;
import fr.sii.ogham.core.builder.condition.RequiredProperties;
import fr.sii.ogham.core.builder.condition.RequiredProperty;
import fr.sii.ogham.core.builder.context.BuildContext;
import fr.sii.ogham.core.builder.env.EnvironmentBuilder;
import fr.sii.ogham.core.builder.priority.ImplementationPriorityProvider;
import fr.sii.ogham.core.builder.priority.PriorityProvider;
import fr.sii.ogham.core.condition.Condition;
import fr.sii.ogham.core.condition.fluent.MessageConditions;
import fr.sii.ogham.core.condition.provider.ImplementationConditionProvider;
import fr.sii.ogham.core.message.Message;
import fr.sii.ogham.core.sender.MessageSender;
import fr.sii.ogham.core.sender.MultiImplementationSender;
/**
* Helps to configure a {@link MultiImplementationSender}.
*
* <p>
* It registers and uses {@link Builder}s to instantiate and configure a
* {@link MessageSender} implementation.
* </p>
*
* <p>
* It also let you provide your own direct {@link MessageSender} implementation.
* </p>
*
* @author Aurélien Baudet
*
* @param <P>
* the type of the parent builder {@link MessageSender}
* {@link Builder}
*/
public class SenderImplementationBuilderHelper<P> {
private static final Logger LOG = LoggerFactory.getLogger(SenderImplementationBuilderHelper.class);
private final P parent;
private final BuildContext buildContext;
private final List<Builder<? extends MessageSender>> senderBuilders;
private final List<MessageSender> customSenders;
private final PriorityProvider<MessageSender> priorityProvider;
/**
* Initializes the builder with a parent builder. The parent builder is used
* when calling and() method of any registered {@link Message}
* {@link Builder}. The {@link EnvironmentBuilder} is used to evaluate
* properties at build time (when
* {@link #addSenders(MultiImplementationSender)} is called).
*
* @param parent
* the parent builder
* @param buildContext
* for registering instances and property evaluation
*/
public SenderImplementationBuilderHelper(P parent, BuildContext buildContext) {
super();
this.parent = parent;
this.buildContext = buildContext;
senderBuilders = new ArrayList<>();
customSenders = new ArrayList<>();
priorityProvider = new ImplementationPriorityProvider<>(buildContext);
}
/**
* Returns true if at least either one custom sender or a sender builder was
* previously registered.
*
* @return true if at least one custom sender or one sender builder
*/
public boolean hasRegisteredSenders() {
return !customSenders.isEmpty() || !senderBuilders.isEmpty();
}
/**
* Registers a custom message sender implementation.
*
* <p>
* If your custom implementation is annotated by one or several of:
* <ul>
* <li>{@link RequiredClass}</li>
* <li>{@link RequiredProperty}</li>
* <li>{@link RequiredClasses}</li>
* <li>{@link RequiredProperties}</li>
* </ul>
* Then if condition evaluation returns true, your implementation will be
* used. If you provide several annotations, your implementation will be
* used only if all conditions are met (and operator).
*
* <p>
* If your custom implementation implements {@link ActivableAtRuntime}, and
* the provided condition evaluation returns true, then your implementation
* will be used.
*
* See {@link MessageConditions} to build your condition.
* </p>
*
* <p>
* If neither annotations nor implementation of {@link ActivableAtRuntime}
* is used, then your custom implementation will be always used. All other
* implementations (even standard ones) will never be used.
* </p>
*
* @param sender
* the sender to register
*/
public void customSender(MessageSender sender) {
customSenders.add(sender);
}
/**
* Registers and configures sender through a dedicated builder.
*
* For example:
*
* <pre>
* .register(JavaMailBuilder.class)
* .host("localhost");
* </pre>
*
* <p>
* If your custom builder is annotated by one or several of:
* <ul>
* <li>{@link RequiredClass}</li>
* <li>{@link RequiredProperty}</li>
* <li>{@link RequiredClasses}</li>
* <li>{@link RequiredProperties}</li>
* </ul>
* Then if condition evaluation returns true, your built implementation will
* be used. If you provide several annotations, your built implementation
* will be used only if all conditions are met (and operator).
*
* <p>
* If your custom builder implements {@link ActivableAtRuntime}, and the
* provided condition evaluation returns true, then your built
* implementation will be used.
*
* See {@link MessageConditions} to build your condition.
* </p>
*
* <p>
* If neither annotations nor implementation of {@link ActivableAtRuntime}
* is used, then your built implementation will be always used. All other
* implementations (even standard ones) will never be used.
* </p>
*
* <p>
* In order to be able to keep chaining, you builder instance may provide a
* constructor with one argument with the type of the parent builder
* ({@code <P>}). If you don't care about chaining, just provide a
* default constructor.
* </p>
*
* <p>
* Your builder may return {@code null} when calling
* {@link Builder#build()}. In this case it means that your implementation
* can't be used due to current environment. Your implementation is then not
* registered.
* </p>
*
* @param builderClass
* the builder class to instantiate
* @param <B>
* the type of the builder
* @return the builder to configure the implementation
*
*/
@SuppressWarnings("unchecked")
public <B extends Builder<? extends MessageSender>> B register(Class<B> builderClass) {
// if builder already registered => provide same instance
for (Builder<? extends MessageSender> builder : senderBuilders) {
if (builderClass.isAssignableFrom(builder.getClass())) {
return (B) builder;
}
}
B builder = instantiateBuilder(builderClass, parent, buildContext);
senderBuilders.add(builder);
return builder;
}
/**
* Add registered custom senders or built senders base on registered sender
* builders to the provided {@link MultiImplementationSender}.
*
* <p>
* A {@link MultiImplementationSender} is able to evaluate a
* {@link Condition} when a message is being sent to determine which
* implementation can and should handle the message and really send it.
* </p>
*
* <p>
* If a custom implementation was registered and is annotated by one or
* several of:
* <ul>
* <li>{@link RequiredClass}</li>
* <li>{@link RequiredProperty}</li>
* <li>{@link RequiredClasses}</li>
* <li>{@link RequiredProperties}</li>
* </ul>
* Then if condition evaluation returns true, the custom implementation will
* be used. If you provide several annotations, the custom implementation
* will be used only if all conditions are met (and operator).
*
* <p>
* If a custom implementation was registered and implements
* {@link ActivableAtRuntime}, and the provided condition evaluation returns
* true, then the custom implementation will be used.
*
* See {@link MessageConditions} to build your condition.
* </p>
*
* <p>
* If neither annotations nor implementation of {@link ActivableAtRuntime}
* is used, then the custom implementation will be always used. All other
* implementations (even standard ones) will never be used.
* </p>
*
* @param mainSender
* the sender that manages several implementations
*/
public void addSenders(MultiImplementationSender<?> mainSender) {
ImplementationConditionProvider implementationSelection = new ImplementationConditionProvider(buildContext.getPropertyResolver());
for (MessageSender customSender : customSenders) {
LOG.debug("Custom implementation {} registered into {}", customSender, mainSender);
mainSender.addImplementation(implementationSelection.provide(customSender), buildContext.register(customSender), priorityProvider.provide(customSender));
}
for (Builder<? extends MessageSender> builder : senderBuilders) {
MessageSender sender = builder.build();
if (sender != null) {
LOG.debug("Implementation {} registered into {}", sender, mainSender);
mainSender.addImplementation(implementationSelection.provide(builder), sender, priorityProvider.provide(sender));
}
}
}
}