Skip to content
This repository has been archived by the owner on Aug 13, 2020. It is now read-only.

Commit

Permalink
Add Sender service
Browse files Browse the repository at this point in the history
Used by handlers to send envelope to the next service component.
  • Loading branch information
Zeeshan Ghalib committed Feb 29, 2016
1 parent 78770f9 commit ccf35fd
Show file tree
Hide file tree
Showing 20 changed files with 756 additions and 25 deletions.
5 changes: 5 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
<artifactId>javax.json</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public DispatcherProducer() {
* @return The correct dispatcher instance.
* @throws IllegalArgumentException if the injection point does not contain any adaptor annotations.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@Produces
public Dispatcher produce(final InjectionPoint injectionPoint) {
final Class targetClass = injectionPoint.getMember().getDeclaringClass();
Expand All @@ -45,6 +46,7 @@ public Dispatcher produce(final InjectionPoint injectionPoint) {
}
}

@SuppressWarnings({"rawtypes", "unchecked"})
void register(@Observes final ServiceComponentFoundEvent event) {
getDispatcher(event.getComponent()).register(instantiateHandler(event.getHandlerBean()));
}
Expand All @@ -60,10 +62,7 @@ private Object instantiateHandler(final Bean<Object> bean) {
}

private AsynchronousDispatcher getDispatcher(final Component component) {
if (!dispatcherMap.containsKey(component)) {
dispatcherMap.put(component, new AsynchronousDispatcher());
}

dispatcherMap.putIfAbsent(component, new AsynchronousDispatcher());
return dispatcherMap.get(component);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
public class AnnotationScanner implements Extension {

@SuppressWarnings("unused")
void afterDeploymentValidation(@Observes final AfterDeploymentValidation event, final BeanManager beanManager) {
beanManager.getBeans(Object.class, new AnnotationLiteral<Any>() {
}).stream().forEach(bean -> getEvent(bean).ifPresent(x -> beanManager.fireEvent(x)));
Expand All @@ -29,6 +30,7 @@ void afterDeploymentValidation(@Observes final AfterDeploymentValidation event,
* @param bean a bean that could be a handler.
* @return an optional event.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
private Optional<ServiceComponentFoundEvent> getEvent(final Bean bean) {
if (bean.getBeanClass().isAnnotationPresent(ServiceComponent.class)) {
return Optional.of(new ServiceComponentFoundEvent(Component.getComponentFromServiceComponent(bean.getBeanClass()), bean));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package uk.gov.justice.services.core.jms;

import uk.gov.justice.services.core.annotation.Component;

import javax.enterprise.context.ApplicationScoped;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* Generates endpoints based on the context.
*/

@ApplicationScoped
public class JmsEndpoints {

private final Map<Component, String> endpointMap;

public JmsEndpoints() {
this.endpointMap = new ConcurrentHashMap<>();
endpointMap.put(Component.COMMAND_CONTROLLER, "%s.controller.commands");
endpointMap.put(Component.COMMAND_HANDLER, "%s.handler.commands");
}

/**
* Retrieves the command controller endpoint based on the <code>component</code> and <code>contextName</code>
*
* @param component Component the endpoint is associated with.
* @param contextName contextName the endpoint is associated with.
* @return the endpoint string for the associated service component and context.
*/
public String getEndpoint(final Component component, final String contextName) {

if (!endpointMap.containsKey(component)) {
throw new IllegalArgumentException("No endpoint defined for component of type " + component);
}

return String.format(endpointMap.get(component), contextName);
}

}
76 changes: 76 additions & 0 deletions core/src/main/java/uk/gov/justice/services/core/jms/JmsSender.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package uk.gov.justice.services.core.jms;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.gov.justice.services.core.jms.exception.JmsSenderException;
import uk.gov.justice.services.core.util.JsonObjectConverter;
import uk.gov.justice.services.messaging.Envelope;

import javax.annotation.Resource;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

@ApplicationScoped
public class JmsSender {

static final String JMS_HEADER_CPPNAME = "CPPNAME";

Logger logger = LoggerFactory.getLogger(JmsSender.class);

@Inject
JsonObjectConverter jsonObjectConverter;

@Resource(mappedName = "java:comp/DefaultJMSConnectionFactory")
QueueConnectionFactory queueConnectionFactory;

Context initialContext;

private Context getInitialContext() throws NamingException {
if (initialContext == null) {
initialContext = new InitialContext();
}
return initialContext;
}


/**
* Sends the <code>envelope</code> to the JMS queue <code>queueName</code>
*
* @param queueName Name of the queue.
* @param envelope Envelope that needs to be sent.
*/
public void send(final String queueName, final Envelope envelope) {

try {
final Queue queue = (Queue) getInitialContext().lookup(queueName);

try (QueueConnection queueConnection = queueConnectionFactory.createQueueConnection()) {

try (QueueSession session = queueConnection.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE)) {

final String envelopeAsString = jsonObjectConverter.asString(jsonObjectConverter.fromEnvelope(envelope));
final TextMessage textMessage = session.createTextMessage(envelopeAsString);
textMessage.setStringProperty(JMS_HEADER_CPPNAME, envelope.metadata().name());

try (QueueSender sender = session.createSender(queue)) {
sender.send(textMessage);
}
}
}

} catch (JMSException | NamingException e) {
throw new JmsSenderException("Exception while sending command to the controller.", e);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package uk.gov.justice.services.core.jms.exception;

public class JmsSenderException extends RuntimeException {

public JmsSenderException(final String message, final Throwable cause) {
super(message, cause);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package uk.gov.justice.services.core.sender;

import uk.gov.justice.services.core.annotation.Component;

import javax.enterprise.context.ApplicationScoped;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static uk.gov.justice.services.core.annotation.Component.COMMAND_API;
import static uk.gov.justice.services.core.annotation.Component.COMMAND_CONTROLLER;
import static uk.gov.justice.services.core.annotation.Component.COMMAND_HANDLER;

/**
* Contains the relationship between the various service components in the same pillar.
*/
@ApplicationScoped
public class ComponentDestination {

private final Map<Component, Component> destinationComponentMap;

public ComponentDestination() {
destinationComponentMap = new ConcurrentHashMap<>();
destinationComponentMap.put(COMMAND_API, COMMAND_CONTROLLER);
destinationComponentMap.put(COMMAND_CONTROLLER, COMMAND_HANDLER);
}

/**
* Get the default destination component for <code>component</code>.
*
* @param component the component whose destination {@link Component} is required.
* @return the default destination {@link Component} for <code>component</code>.
*/
public Component getDefault(final Component component) {
if (!destinationComponentMap.containsKey(component)) {
throw new IllegalArgumentException("No default destination defined for service component of type " + component);
}

return destinationComponentMap.get(component);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package uk.gov.justice.services.core.sender;


import uk.gov.justice.services.core.annotation.Component;
import uk.gov.justice.services.core.jms.JmsEndpoints;
import uk.gov.justice.services.core.jms.JmsSender;
import uk.gov.justice.services.core.util.CoreUtil;
import uk.gov.justice.services.messaging.Envelope;

import javax.enterprise.inject.Alternative;
import java.util.Objects;

/**
* Sends an action to the next layer using JMS Sender.
*/

@Alternative
public class DefaultSender implements Sender {

private final JmsSender jmsSender;
private final Component destinationComponent;
private final JmsEndpoints jmsEndpoints;

DefaultSender(final JmsSender jmsSender, final Component destinationComponent, final JmsEndpoints jmsEndpoints) {
this.jmsSender = jmsSender;
this.destinationComponent = destinationComponent;
this.jmsEndpoints = jmsEndpoints;
}

@Override
public void send(final Envelope envelope) {
final String contextName = CoreUtil.extractContextNameFromActionOrEventName(envelope.metadata().name());
jmsSender.send(jmsEndpoints.getEndpoint(destinationComponent, contextName), envelope);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DefaultSender that = (DefaultSender) o;
return Objects.equals(jmsSender, that.jmsSender) &&
destinationComponent == that.destinationComponent &&
Objects.equals(jmsEndpoints, that.jmsEndpoints);
}

@Override
public int hashCode() {
return Objects.hash(jmsSender, destinationComponent, jmsEndpoints);
}
}
16 changes: 16 additions & 0 deletions core/src/main/java/uk/gov/justice/services/core/sender/Sender.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package uk.gov.justice.services.core.sender;

import uk.gov.justice.services.messaging.Envelope;

/**
* Sends an action to the next layer.
*/
public interface Sender {

/**
* Sends envelope to the next component. The correct sender is injected by the framework.
*
* @param envelope Envelope that needs to be sent.
*/
void send(final Envelope envelope);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package uk.gov.justice.services.core.sender;

import uk.gov.justice.services.core.annotation.Component;
import uk.gov.justice.services.core.annotation.ServiceComponent;
import uk.gov.justice.services.core.jms.JmsEndpoints;
import uk.gov.justice.services.core.jms.JmsSender;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.inject.Inject;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* Produces the correct Sender based on the injection point.
*/

@ApplicationScoped
public class SenderProducer {

@Inject
JmsSender jmsSender;

@Inject
JmsEndpoints jmsEndpoints;

@Inject
ComponentDestination componentDestination;

private Map<Component, Sender> senderMap;

public SenderProducer() {
senderMap = new ConcurrentHashMap<>();
}

/**
* Produces the correct Sender based on the injection point.
*
* @param injectionPoint injection point where the Sender is being injected into.
* @return An implementation of the Sender.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@Produces
public Sender produce(final InjectionPoint injectionPoint) {
final Class targetClass = injectionPoint.getMember().getDeclaringClass();

if (targetClass.isAnnotationPresent(ServiceComponent.class)) {
return getSender(Component.getComponentFromServiceComponent(targetClass));
} else {
throw new IllegalArgumentException("InjectionPoint class must be annotated with " + ServiceComponent.class);
}
}

private Sender getSender(final Component component) {
senderMap.putIfAbsent(component, new DefaultSender(jmsSender, componentDestination.getDefault(component), jmsEndpoints));
return senderMap.get(component);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ private CoreUtil() {
*/
public static String extractContextNameFromActionOrEventName(final String actionOrEventName) {
if (!actionOrEventName.contains(".")) {
throw new IllegalArgumentException("Invalid action or event name.");
throw new IllegalArgumentException("Invalid action or event name " + actionOrEventName);
} else return actionOrEventName.split("\\.")[0];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
@ApplicationScoped
public class JsonObjectConverter {

public static String METADATA = "_metadata";
public static final String METADATA = "_metadata";

/**
* Converts a json string into a JsonObject.
Expand Down
Loading

0 comments on commit ccf35fd

Please sign in to comment.