diff --git a/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/builder/ActionBuilder.java b/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/builder/ActionBuilder.java new file mode 100644 index 000000000..30a47fd96 --- /dev/null +++ b/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/builder/ActionBuilder.java @@ -0,0 +1,47 @@ +package uk.gov.justice.services.adapters.test.utils.builder; + +import org.raml.model.Action; +import org.raml.model.ActionType; +import org.raml.model.MimeType; + +import java.util.HashMap; +import java.util.Map; + +public class ActionBuilder { + private ActionType actionType; + private final Map body = new HashMap<>(); + + public static ActionBuilder action() { + return new ActionBuilder(); + } + + public static ActionBuilder action(final ActionType actionType, final String... mimeTypes) { + ActionBuilder actionBuilder = new ActionBuilder() + .with(actionType); + for (String mimeType: mimeTypes) { + actionBuilder = actionBuilder.withMediaType(mimeType); + } + return actionBuilder; + } + + public ActionBuilder with(final ActionType actionType) { + this.actionType = actionType; + return this; + } + + public ActionBuilder withMediaType(final MimeType mimeType) { + body.put(mimeType.toString(), mimeType); + return this; + } + public ActionBuilder withMediaType(String stringMimeType) { + return withMediaType(new MimeType(stringMimeType)); + } + + public Action build() { + final Action action = new Action(); + action.setType(actionType); + action.setBody(body); + return action; + } + +} diff --git a/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/builder/RamlBuilder.java b/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/builder/RamlBuilder.java new file mode 100644 index 000000000..66ddcd174 --- /dev/null +++ b/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/builder/RamlBuilder.java @@ -0,0 +1,73 @@ +package uk.gov.justice.services.adapters.test.utils.builder; + +import org.raml.model.ActionType; +import org.raml.model.Raml; +import org.raml.model.Resource; + +import static uk.gov.justice.services.adapters.test.utils.builder.ActionBuilder.action; +import static uk.gov.justice.services.adapters.test.utils.builder.ResourceBuilder.resource; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RamlBuilder { + private final List resourceBuilders = new ArrayList<>(); + private String version; + private String title; + private String baseUri; + + public static RamlBuilder raml() { + return new RamlBuilder(); + } + + public static RamlBuilder restRamlWithDefaults() { + return new RamlBuilder() + .withVersion("#%RAML 0.8") + .withTitle("Example Service"); + } + + public RamlBuilder with(final ResourceBuilder resource) { + resourceBuilders.add(resource); + return this; + } + + public RamlBuilder withDefaults() { + return this.with(resource() + .withRelativeUri("/somecontext.controller.commands") + .with(action().with(ActionType.POST))); + } + + public RamlBuilder withVersion(final String version) { + this.version = version; + return this; + } + + public RamlBuilder withTitle(final String title) { + this.title = title; + return this; + } + + public RamlBuilder withBaseUri(final String baseUri) { + this.baseUri = baseUri; + return this; + } + + public Raml build() { + Raml raml = new Raml(); + + raml.setBaseUri(baseUri); + raml.setVersion(version); + raml.setTitle(title); + + Map resources = new HashMap<>(); + for (ResourceBuilder resourceBuilder : resourceBuilders) { + Resource resource = resourceBuilder.build(); + resources.put(resource.getRelativeUri(), resource); + } + + raml.setResources(resources); + return raml; + } +} \ No newline at end of file diff --git a/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/builder/ResourceBuilder.java b/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/builder/ResourceBuilder.java new file mode 100644 index 000000000..0fe90c82e --- /dev/null +++ b/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/builder/ResourceBuilder.java @@ -0,0 +1,70 @@ +package uk.gov.justice.services.adapters.test.utils.builder; + +import org.raml.model.Action; +import org.raml.model.ActionType; +import org.raml.model.Resource; +import org.raml.model.parameter.UriParameter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import static uk.gov.justice.services.adapters.test.utils.builder.ActionBuilder.action; + +public class ResourceBuilder { + private final List actionBuilders = new ArrayList<>(); + + private String parentUri = ""; + private String relativeUri = "/somecontext.controller.commands"; + private Map uriParameters = new HashMap<>(); + + public static ResourceBuilder resource() { + return new ResourceBuilder(); + } + + public static ResourceBuilder resource(final String relativeUri, final String... pathParams) { + ResourceBuilder resourceBuilder = new ResourceBuilder() + .withRelativeUri(relativeUri); + for(String pathParam: pathParams) { + resourceBuilder = resourceBuilder.withPathParam(pathParam); + } + return resourceBuilder; + } + + public ResourceBuilder with(final ActionBuilder action) { + actionBuilders.add(action); + return this; + } + + public ResourceBuilder withRelativeUri(final String uri) { + relativeUri = uri; + return this; + } + + public ResourceBuilder withPathParam(final String name) { + uriParameters.put(name, new UriParameter(name)); + return this; + } + + public Resource build() { + final Resource resource = new Resource(); + resource.setParentUri(parentUri); + resource.setRelativeUri(relativeUri); + resource.setUriParameters(uriParameters); + + Map actions = new HashMap<>(); + for (ActionBuilder actionBuilder : actionBuilders) { + Action action = actionBuilder.build(); + action.setResource(resource); + actions.put(action.getType(), action); + } + + resource.setActions(actions); + return resource; + } + + public ResourceBuilder withDefaultAction() { + with(action().with(ActionType.POST)); + return this; + } +} diff --git a/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/compiler/AllObjectsScanner.java b/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/compiler/AllObjectsScanner.java new file mode 100644 index 000000000..df8e7b84c --- /dev/null +++ b/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/compiler/AllObjectsScanner.java @@ -0,0 +1,30 @@ +package uk.gov.justice.services.adapters.test.utils.compiler; + +import org.reflections.scanners.SubTypesScanner; + +/** + * Extension of the reflection API to scan for all classes + * + */ +public class AllObjectsScanner extends SubTypesScanner { + + public AllObjectsScanner() { + super(false); + } + + + /** + * Accepts all classes and puts them in the store as subclasses of @Object + * + */ + @SuppressWarnings({"unchecked"}) + public void scan(final Object cls) { + String className = getMetadataAdapter().getClassName(cls); + String superclass = Object.class.getName(); + + if (acceptResult(superclass)) { + getStore().put(superclass, className); + } + } + +} diff --git a/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/compiler/CompilationException.java b/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/compiler/CompilationException.java new file mode 100644 index 000000000..4d2695b81 --- /dev/null +++ b/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/compiler/CompilationException.java @@ -0,0 +1,14 @@ +package uk.gov.justice.services.adapters.test.utils.compiler; + +public class CompilationException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public CompilationException(String string) { + super(string); + } + + + public CompilationException(Throwable throwable) { + super(throwable); + } +} diff --git a/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/compiler/JavaCompilerUtil.java b/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/compiler/JavaCompilerUtil.java new file mode 100644 index 000000000..eed02ef9e --- /dev/null +++ b/adapters/adapters-test-utils/src/main/java/uk/gov/justice/services/adapters/test/utils/compiler/JavaCompilerUtil.java @@ -0,0 +1,191 @@ +package uk.gov.justice.services.adapters.test.utils.compiler; + +import com.google.common.collect.Sets; +import org.apache.commons.io.FileUtils; +import org.reflections.ReflectionUtils; +import org.reflections.Reflections; +import org.reflections.util.ClasspathHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.tools.Diagnostic; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static java.text.MessageFormat.format; + +/** + * Compiles and loads classes and interfaces from the specified folders + */ +public class JavaCompilerUtil { + private static final Logger LOG = LoggerFactory.getLogger(JavaCompilerUtil.class); + private final File codegenOutputDir, compilationOutputDir; + + public JavaCompilerUtil(File codegenOutputDir, File compilationOutputDir) { + this.codegenOutputDir = codegenOutputDir; + this.compilationOutputDir = compilationOutputDir; + } + + /** + * Compiles and loads a single class + * @param basePackage + * + * @return + * @throws MalformedURLException + * @throws IllegalStateException + * - if more or less than one classes found + */ + public Class compiledClassOf(String basePackage) + throws MalformedURLException { + Set> resourceClasses = compiledClassesOf(basePackage); + if (resourceClasses.size() != 1) { + throw new IllegalStateException(format("Expected to find single class but found {0}", resourceClasses)); + } + return resourceClasses.iterator().next(); + } + + /** + * Compiles and loads a single interface + * @param basePackageName + * + * @return + * @throws MalformedURLException + * @throws IllegalStateException + * - if more or less than one interfaces found + */ + public Class compiledInterfaceOf(String basePackageName) + throws MalformedURLException { + Set> resourceInterfaces = compiledInterfacesOf(basePackageName); + if (resourceInterfaces.size() != 1) { + throw new IllegalStateException( + format("Expected to find single interface but found {0}", resourceInterfaces)); + + } + return resourceInterfaces.iterator().next(); + } + + /** + * compiles and loads specified classes + * @param basePackage + * + * @return + * @throws MalformedURLException + */ + public Set> compiledClassesOf(String basePackage) + throws MalformedURLException { + return compiledClassesAndInterfaces(c -> !c.isInterface(), basePackage); + } + + /** + * compiles and loads specified interfaces + * @param basePackage + * + * @return + * @throws MalformedURLException + */ + public Set> compiledInterfacesOf(String basePackage) + throws MalformedURLException { + return compiledClassesAndInterfaces(c -> c.isInterface(), basePackage); + } + + private Set> compiledClassesAndInterfaces(Predicate> predicate, + String basePackage) + throws MalformedURLException { + return compile(basePackage).stream().filter(predicate).collect(Collectors.toSet()); + } + + private Set> compile(String basePackage) throws MalformedURLException { + compile(); + return loadClasses(basePackage); + } + + private Set> loadClasses(String basePackage) throws MalformedURLException { + + ClassLoader initialClassLoader = Thread.currentThread().getContextClassLoader(); + Set> rootResourceClasses = new HashSet<>(); + try(URLClassLoader resourceClassLoader = new URLClassLoader(new URL[] { compilationOutputDir.toURI().toURL() })) { + Thread.currentThread().setContextClassLoader(resourceClassLoader); + Reflections reflections = new Reflections(basePackage, ClasspathHelper.forClass(Object.class), + new AllObjectsScanner()); + Set classNames = reflections.getStore().get(AllObjectsScanner.class, Object.class.getName()); + rootResourceClasses.addAll(Sets.newHashSet(ReflectionUtils.forNames(classNames, reflections.getConfiguration().getClassLoaders()))); + } catch(IOException ex) { + throw new RuntimeException("Error creating class loader", ex); + } finally { + Thread.currentThread().setContextClassLoader(initialClassLoader); + } + return rootResourceClasses; + } + + private void compile() { + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + + DiagnosticCollector diagnostics = new DiagnosticCollector(); + StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, Locale.getDefault(), null); + List javaObjects = scanRecursivelyForJavaObjects(codegenOutputDir, fileManager); + + if (javaObjects.size() == 0) { + throw new CompilationException( + format("There are no source files to compile in {0}", codegenOutputDir.getAbsolutePath())); + } + String[] compileOptions = new String[] { "-d", compilationOutputDir.getAbsolutePath() }; + Iterable compilationOptions = Arrays.asList(compileOptions); + + CompilationTask compilerTask = compiler.getTask(null, fileManager, diagnostics, compilationOptions, null, + javaObjects); + + if (!compilerTask.call()) { + for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { + LOG.error("Error on line {} in {}", diagnostic.getLineNumber(), diagnostic); + } + throw new CompilationException("Could not compile project"); + } + } + + private List scanRecursivelyForJavaObjects(File dir, StandardJavaFileManager fileManager) { + List javaObjects = new LinkedList(); + File[] files = dir.listFiles(); + for (File file : files) { + if (file.isDirectory()) { + javaObjects.addAll(scanRecursivelyForJavaObjects(file, fileManager)); + } else if (file.isFile() && file.getName().toLowerCase().endsWith(".java")) { + try { + LOG.debug(FileUtils.readFileToString(file)); + } catch (IOException e) { + LOG.warn("Could not read file", e); + } + javaObjects.add(readJavaObject(file, fileManager)); + } + } + return javaObjects; + } + + private JavaFileObject readJavaObject(File file, StandardJavaFileManager fileManager) { + Iterable javaFileObjects = fileManager.getJavaFileObjects(file); + Iterator it = javaFileObjects.iterator(); + if (it.hasNext()) { + return it.next(); + } + throw new CompilationException(format("Could not load {0} java file object", file.getAbsolutePath())); + } + +} diff --git a/adapters/jms-adapter-generator/pom.xml b/adapters/jms-adapter-generator/pom.xml new file mode 100644 index 000000000..e258c16fc --- /dev/null +++ b/adapters/jms-adapter-generator/pom.xml @@ -0,0 +1,56 @@ + + 4.0.0 + + adapters + uk.gov.justice.services + 0.1.0-SNAPSHOT + + jms-adapter-generator + + + javax + javaee-api + + + org.slf4j + slf4j-api + + + uk.gov.justice + raml-generator-core + ${raml-maven-plugin.version} + + + org.raml + raml-parser + + + uk.gov.justice.services + core + ${project.version} + + + junit + junit + test + + + org.hamcrest + hamcrest-library + test + + + org.mockito + mockito-core + test + + + uk.gov.justice.services + adapters-test-utils + ${project.version} + test + + + + \ No newline at end of file diff --git a/adapters/jms-adapter-generator/src/main/java/uk/gov/justice/raml/jms/core/JmsEndpointGenerator.java b/adapters/jms-adapter-generator/src/main/java/uk/gov/justice/raml/jms/core/JmsEndpointGenerator.java new file mode 100644 index 000000000..89e256e4b --- /dev/null +++ b/adapters/jms-adapter-generator/src/main/java/uk/gov/justice/raml/jms/core/JmsEndpointGenerator.java @@ -0,0 +1,189 @@ +package uk.gov.justice.raml.jms.core; + +import org.apache.commons.lang.StringUtils; +import org.raml.model.Action; +import org.raml.model.ActionType; +import org.raml.model.Raml; +import org.raml.model.Resource; +import uk.gov.justice.raml.core.Generator; +import uk.gov.justice.raml.core.GeneratorConfig; +import uk.gov.justice.services.core.annotation.Component; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.lang.String.format; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.joining; +import static org.apache.commons.io.FileUtils.write; +import static uk.gov.justice.raml.jms.core.TemplateRenderer.render; + +/** + * Generates JMS endpoint classes out of RAML object + */ +public class JmsEndpointGenerator implements Generator { + + private static final String UTF_8 = "UTF-8"; + private static final String TEMPLATE_LOADING_ERROR = "Failed to load template resource JmsListenerTemplate.tpm"; + private static final String ACTIONS_EMPTY_ERROR = "No actions to process"; + private static final String OUTPUT_FILE_GENERATION_ERROR = "Failed to create output file for %s"; + private static final String FILENAME_POSTFIX = "JmsListener.java"; + private static final String JMS_TEMPLATE_RESOURCE = "JmsListenerTemplate.tpm"; + private static final Pattern MEDIA_TYPE_PATTERN = Pattern.compile("(application/vnd.)(\\S+)(\\+\\S+)"); + private static final String EMPTY = ""; + + /** + * Generates JMS endpoint classes out of RAML object + * + * @param raml - the RAML object + * @param configuration - contains package of generated sources, as well as source and + * destination folders + */ + @Override + public void run(final Raml raml, final GeneratorConfig configuration) { + final Collection ramlResourceModels = raml.getResources().values(); + ramlResourceModels.stream() + .map(resource -> templateAttributesFrom(resource, configuration)) + .forEach(attribute -> writeToTemplateFile(attribute, jmsListenerTemplate(), outputDirFrom(configuration))); + + } + + private File outputDirFrom(final GeneratorConfig configuration) { + return new File(format("%s/%s", configuration.getOutputDirectory(), + configuration.getBasePackageName().replace(".", "/"))); + } + + @SuppressWarnings("resource") + private String jmsListenerTemplate() { + try (final InputStream stream = getClass().getResourceAsStream(JMS_TEMPLATE_RESOURCE)) { + return new Scanner(stream, UTF_8).useDelimiter("\\A").next(); + } catch (IOException e) { + throw new JmsEndpointGeneratorException(TEMPLATE_LOADING_ERROR, e); + } + } + + private void writeToTemplateFile(final Attributes attributes, final String jmsTemplate, + final File outputDirectory) { + final File file = new File(outputDirectory, createJmsFilenameFrom(attributes.uri)); + try { + write(file, render(jmsTemplate, attributes.attributesMap)); + } catch (IOException e) { + throw new JmsEndpointGeneratorException(format(OUTPUT_FILE_GENERATION_ERROR, attributes.uri), e); + } + } + + private String createJmsFilenameFrom(final String uri) { + return classNameOf(uri) + FILENAME_POSTFIX; + } + + /** + * Create Template Attributes from the RAML Resource and Configuration + * + * @param resource RAML Resource + * @param configuration Configuration information + * @return template attributes + */ + private Attributes templateAttributesFrom(final Resource resource, final GeneratorConfig configuration) { + final String uri = resource.getUri(); + final HashMap data = new HashMap<>(); + + data.put("PACKAGE_NAME", configuration.getBasePackageName()); + data.put("CLASS_NAME", classNameOf(uri)); + data.put("ADAPTER_TYPE", componentOf(uri).name()); + data.put("DESTINATION_LOOKUP", destinationNameOf(uri)); + data.put("MESSAGE_SELECTOR", messageSelectorsFrom(resource.getActions())); + + return new Attributes(data, uri); + } + + /** + * Convert given URI to a camel cased class name + * + * @param uri URI String to convert + * @return camel case class name + */ + private String classNameOf(final String uri) { + return stream(uri.split("/|\\.")) + .map(StringUtils::capitalize) + .collect(joining(EMPTY)); + } + + /** + * Convert given URI to a valid Component + * + * Takes the last and second to last parts of the URI as the pillar and tier of the Component + * + * @param uri URI String to convert + * @return component the value of the pillar and tier parts of the uri + */ + private Component componentOf(final String uri) { + final String[] uriParts = uri.split("\\."); + return Component.valueOf(uriParts[uriParts.length - 1], uriParts[uriParts.length - 2]); + } + + /** + * Construct the destination name from the URI + * @param uri URI String to convert + * @return destination name + */ + private String destinationNameOf(final String uri) { + return uri.replaceAll("/", ""); + } + + /** + * Parse and format all the message selectors from the Post Action + * + * @param actions Map of ActionType to Action + * @return formatted message selector String + */ + private String messageSelectorsFrom(final Map actions) { + if (actions.isEmpty()) { + throw new JmsEndpointGeneratorException(ACTIONS_EMPTY_ERROR); + } + return format("'%s'", parse(actions.get(ActionType.POST))); + } + + /** + * Parse an Action into a message selectors String + * + * @param action Action to parse + * @return formatted message selectors String + */ + private String parse(final Action action) { + return action.getBody().keySet().stream() + .map(this::commandNameOf) + .collect(joining("','")); + } + + /** + * Converts media type String to a command name + * + * Command name is equal to everything between "application/vnd." and the first "+". + * + * @param mediaType String representation of the Media Type + * @return command name + */ + private String commandNameOf(final String mediaType) { + final Matcher m = MEDIA_TYPE_PATTERN.matcher(mediaType); + m.find(); + return m.group(2); + } + + private class Attributes { + final Map attributesMap; + final String uri; + + public Attributes(final Map attributesMap, final String uri) { + this.attributesMap = attributesMap; + this.uri = uri; + } + } + +} diff --git a/adapters/jms-adapter-generator/src/main/java/uk/gov/justice/raml/jms/core/JmsEndpointGeneratorException.java b/adapters/jms-adapter-generator/src/main/java/uk/gov/justice/raml/jms/core/JmsEndpointGeneratorException.java new file mode 100644 index 000000000..3a646f6d7 --- /dev/null +++ b/adapters/jms-adapter-generator/src/main/java/uk/gov/justice/raml/jms/core/JmsEndpointGeneratorException.java @@ -0,0 +1,13 @@ +package uk.gov.justice.raml.jms.core; + +public class JmsEndpointGeneratorException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public JmsEndpointGeneratorException(final String message, final Throwable e) { + super(message, e); + } + + public JmsEndpointGeneratorException(final String message) { + super(message); + } +} diff --git a/adapters/jms-adapter-generator/src/main/java/uk/gov/justice/raml/jms/core/TemplateRenderer.java b/adapters/jms-adapter-generator/src/main/java/uk/gov/justice/raml/jms/core/TemplateRenderer.java new file mode 100644 index 000000000..a918df75d --- /dev/null +++ b/adapters/jms-adapter-generator/src/main/java/uk/gov/justice/raml/jms/core/TemplateRenderer.java @@ -0,0 +1,29 @@ +package uk.gov.justice.raml.jms.core; + +import static java.lang.String.format; + +import java.util.Map; + +import org.apache.commons.lang.text.StrBuilder; + +/** + * Basic template rendering class + * + */ +public class TemplateRenderer { + public static final String ATTRIBUTE_KEY_FORMAT = "${%s}"; + + private TemplateRenderer() { + } + + /** + * @param template - string containing template + * @param attributes - attribute keys and values to be used in rendering of the template + * @return - rendered template + */ + public static String render(final String template, final Map attributes) { + final StrBuilder stringBuilder = new StrBuilder(template); + attributes.entrySet().forEach(e -> stringBuilder.replaceAll(format(ATTRIBUTE_KEY_FORMAT, e.getKey()), e.getValue())); + return stringBuilder.toString(); + } +} diff --git a/adapters/jms-adapter-generator/src/main/resources/uk/gov/justice/raml/jms/core/JmsListenerTemplate.tpm b/adapters/jms-adapter-generator/src/main/resources/uk/gov/justice/raml/jms/core/JmsListenerTemplate.tpm new file mode 100644 index 000000000..0e53b23c8 --- /dev/null +++ b/adapters/jms-adapter-generator/src/main/resources/uk/gov/justice/raml/jms/core/JmsListenerTemplate.tpm @@ -0,0 +1,25 @@ +package ${PACKAGE_NAME}; + +import javax.inject.Inject; +import javax.ejb.MessageDriven; +import javax.ejb.ActivationConfigProperty; +import uk.gov.justice.services.core.annotation.Adapter; +import uk.gov.justice.services.core.dispatcher.Dispatcher; +import uk.gov.justice.services.core.jms.AbstractJMSListener; +import static uk.gov.justice.services.core.annotation.Component.*; + +@MessageDriven(activationConfig = { + @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "${DESTINATION_LOOKUP}"), + @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), + @ActivationConfigProperty(propertyName = "messageSelector", propertyValue = "CPPNAME in(${MESSAGE_SELECTOR})") +}) +@Adapter(${ADAPTER_TYPE}) +public class ${CLASS_NAME}JmsListener extends AbstractJMSListener { + + @Inject + Dispatcher dispatcher; + + protected Dispatcher getDispatcher() { + return dispatcher; + } +} \ No newline at end of file diff --git a/adapters/jms-adapter-generator/src/test/java/uk/gov/justice/raml/jms/core/JmsEndpointGeneratorTest.java b/adapters/jms-adapter-generator/src/test/java/uk/gov/justice/raml/jms/core/JmsEndpointGeneratorTest.java new file mode 100644 index 000000000..c1c094951 --- /dev/null +++ b/adapters/jms-adapter-generator/src/test/java/uk/gov/justice/raml/jms/core/JmsEndpointGeneratorTest.java @@ -0,0 +1,385 @@ +package uk.gov.justice.raml.jms.core; + +import org.hamcrest.FeatureMatcher; +import org.hamcrest.Matcher; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.raml.model.ActionType; +import uk.gov.justice.raml.core.Generator; +import uk.gov.justice.raml.core.GeneratorConfig; +import uk.gov.justice.services.adapters.test.utils.compiler.JavaCompilerUtil; +import uk.gov.justice.services.core.annotation.Adapter; +import uk.gov.justice.services.core.dispatcher.Dispatcher; +import uk.gov.justice.services.core.jms.AbstractJMSListener; +import uk.gov.justice.services.messaging.Envelope; + +import javax.ejb.ActivationConfigProperty; +import javax.ejb.MessageDriven; +import javax.inject.Inject; +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.arrayContainingInAnyOrder; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItemInArray; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.core.IsNot.not; +import static org.hamcrest.core.StringStartsWith.startsWith; +import static org.junit.Assert.assertThat; +import static org.raml.model.ActionType.DELETE; +import static org.raml.model.ActionType.GET; +import static org.raml.model.ActionType.HEAD; +import static org.raml.model.ActionType.OPTIONS; +import static org.raml.model.ActionType.PATCH; +import static org.raml.model.ActionType.POST; +import static org.raml.model.ActionType.TRACE; +import static uk.gov.justice.services.adapters.test.utils.builder.ActionBuilder.action; +import static uk.gov.justice.services.adapters.test.utils.builder.RamlBuilder.raml; +import static uk.gov.justice.services.adapters.test.utils.builder.ResourceBuilder.resource; +import static uk.gov.justice.services.core.annotation.Component.COMMAND_CONTROLLER; +import static uk.gov.justice.services.core.annotation.Component.COMMAND_HANDLER; + +public class JmsEndpointGeneratorTest { + + private static final String BASE_PACKAGE = "uk.test"; + private static final String BASE_PACKAGE_FOLDER = "/uk/test"; + + private Generator generator = new JmsEndpointGenerator(); + + @Rule + public TemporaryFolder outputFolder = new TemporaryFolder(); + private JavaCompilerUtil compiler; + + @Before + public void setup() throws Exception { + compiler = new JavaCompilerUtil(outputFolder.getRoot(), outputFolder.getRoot()); + } + + @Test + public void shouldCreateJmsClass() throws Exception { + generator.run( + raml() + .with(resource() + .withRelativeUri("/structure.controller.commands") + .withDefaultAction()) + .build(), + configurationWithBasePackage(BASE_PACKAGE)); + + File packageDir = new File(outputFolder.getRoot().getAbsolutePath() + BASE_PACKAGE_FOLDER); + File[] files = packageDir.listFiles(); + assertThat(files.length, is(1)); + assertThat(files[0].getName(), is("StructureControllerCommandsJmsListener.java")); + } + + @SuppressWarnings("unchecked") + @Test + public void shouldCreateMultipleJmsClasses() throws Exception { + generator.run( + raml() + .with(resource() + .withRelativeUri("/structure.controller.commands") + .withDefaultAction()) + .with(resource() + .withRelativeUri("/people.controller.commands") + .withDefaultAction()) + .build(), + configurationWithBasePackage(BASE_PACKAGE)); + + File packageDir = new File(outputFolder.getRoot().getAbsolutePath() + BASE_PACKAGE_FOLDER); + File[] files = packageDir.listFiles(); + assertThat(files.length, is(2)); + assertThat(files, + arrayContainingInAnyOrder(hasProperty("name", equalTo("PeopleControllerCommandsJmsListener.java")), + hasProperty("name", equalTo("StructureControllerCommandsJmsListener.java")))); + + } + + @Test + public void shouldOverwriteJmsClass() throws Exception { + String path = outputFolder.getRoot().getAbsolutePath() + BASE_PACKAGE_FOLDER; + File packageDir = new File(path); + packageDir.mkdirs(); + Files.write(Paths.get(path + "/StructureControllerCommandsJmsListener.java"), + asList("Old file content")); + + generator.run( + raml() + .with(resource() + .withRelativeUri("/structure.controller.commands") + .withDefaultAction()) + .build(), + configurationWithBasePackage(BASE_PACKAGE)); + + List lines = Files.readAllLines(Paths.get(path + "/StructureControllerCommandsJmsListener.java")); + assertThat(lines.get(0), not(containsString("Old file content"))); + } + + @Test + public void shouldCreateJmsEndpointNamedAfterResourceUri() throws Exception { + generator.run( + raml() + .with(resource() + .withRelativeUri("/structure.controller.commands") + .withDefaultAction()) + .build(), + configurationWithBasePackage("uk.somepackage")); + + Class compiledClass = compiler.compiledClassOf("uk.somepackage"); + assertThat(compiledClass.getName(), is("uk.somepackage.StructureControllerCommandsJmsListener")); + } + + @Test + public void shouldCreateJmsEndpointInADifferentPackage() throws Exception { + generator.run( + raml() + .with(resource() + .withRelativeUri("/structure.controller.commands") + .withDefaultAction()) + .build(), + configurationWithBasePackage("uk.package2")); + + Class clazz = compiler.compiledClassOf("uk.package2"); + assertThat(clazz.getName(), is("uk.package2.StructureControllerCommandsJmsListener")); + } + + @Test + public void shouldCreateJmsEndpointAnnotatedWithCommandHandlerAdapter() throws Exception { + generator.run( + raml() + .with(resource() + .withRelativeUri("/people.handler.commands") + .with(action().with(ActionType.POST))) + .build(), + configurationWithBasePackage(BASE_PACKAGE)); + Class clazz = compiler.compiledClassOf(BASE_PACKAGE); + Adapter adapterAnnotation = clazz.getAnnotation(Adapter.class); + assertThat(adapterAnnotation, not(nullValue())); + assertThat(adapterAnnotation.value(), is(COMMAND_HANDLER)); + + } + + @Test + public void shouldCreateJmsEndpointAnnotatedWithCommandControllerAdapter() throws Exception { + generator.run( + raml() + .with(resource() + .withRelativeUri("/people.controller.commands") + .with(action().with(ActionType.POST))) + .build(), + configurationWithBasePackage(BASE_PACKAGE)); + + Class clazz = compiler.compiledClassOf(BASE_PACKAGE); + Adapter adapterAnnotation = clazz.getAnnotation(Adapter.class); + assertThat(adapterAnnotation, not(nullValue())); + assertThat(adapterAnnotation.value(), is(COMMAND_CONTROLLER)); + + } + + @Test + public void shouldCreateJmsEndpointExtendingAbstractJmsListener() throws Exception { + generator.run(raml().withDefaults().build(), configurationWithBasePackage(BASE_PACKAGE)); + + Class clazz = compiler.compiledClassOf(BASE_PACKAGE); + assertThat(clazz.getSuperclass(), equalTo(AbstractJMSListener.class)); + } + + @Test + public void shouldCreateJmsEndpointWithAnnotatedDispatcherProperty() throws Exception { + generator.run(raml().withDefaults().build(), configurationWithBasePackage(BASE_PACKAGE)); + + Class clazz = compiler.compiledClassOf(BASE_PACKAGE); + Field dispatcherField = clazz.getDeclaredField("dispatcher"); + assertThat(dispatcherField, not(nullValue())); + assertThat(dispatcherField.getAnnotations(), arrayWithSize(1)); + assertThat(dispatcherField.getAnnotation(Inject.class), not(nullValue())); + } + + @Test + public void shouldCreateAnnotatedJmsEndpointWithDestinationLookupProperty() throws Exception { + generator.run( + raml() + .with(resource() + .withRelativeUri("/people.controller.commands") + .with(action().with(ActionType.POST))) + .build(), + configurationWithBasePackage(BASE_PACKAGE)); + + Class clazz = compiler.compiledClassOf(BASE_PACKAGE); + assertThat(clazz.getAnnotation(MessageDriven.class), is(notNullValue())); + assertThat(clazz.getAnnotation(MessageDriven.class).activationConfig(), + hasItemInArray(allOf(propertyName(equalTo("destinationLookup")), + propertyValue(equalTo("people.controller.commands"))))); + } + + @Test + public void shouldCreateAnnotatedJmsEndpointWithDestinationLookupProperty2() throws Exception { + generator.run( + raml() + .with(resource() + .withRelativeUri("/structure.controller.commands") + .with(action().with(ActionType.POST))) + .build(), + configurationWithBasePackage(BASE_PACKAGE)); + + Class clazz = compiler.compiledClassOf(BASE_PACKAGE); + assertThat(clazz.getAnnotation(MessageDriven.class), is(notNullValue())); + assertThat(clazz.getAnnotation(MessageDriven.class).activationConfig(), + hasItemInArray(allOf(propertyName(equalTo("destinationLookup")), + propertyValue(equalTo("structure.controller.commands"))))); + } + + @Test + public void shouldCreateAnnotatedJmsEndpointWithDestinationType() throws Exception { + generator.run(raml().withDefaults().build(), configurationWithBasePackage(BASE_PACKAGE)); + + Class clazz = compiler.compiledClassOf(BASE_PACKAGE); + assertThat(clazz.getAnnotation(MessageDriven.class), is(notNullValue())); + assertThat(clazz.getAnnotation(MessageDriven.class).activationConfig(), + hasItemInArray(allOf(propertyName(equalTo("destinationType")), + propertyValue(equalTo("javax.jms.Queue"))))); + } + + @Test + public void shouldCreateAnnotatedJmsEndpointWithMessageSelectorContainingOneCommandWithAPost() throws Exception { + generator.run( + raml() + .with(resource() + .with(action() + .with(ActionType.POST) + .withMediaType("application/vnd.structure.commands.test-cmd+json"))) + .build(), + configurationWithBasePackage(BASE_PACKAGE)); + + Class clazz = compiler.compiledClassOf(BASE_PACKAGE); + assertThat(clazz.getAnnotation(MessageDriven.class), is(notNullValue())); + assertThat(clazz.getAnnotation(MessageDriven.class).activationConfig(), + hasItemInArray(allOf(propertyName(equalTo("messageSelector")), + propertyValue(equalTo("CPPNAME in('structure.commands.test-cmd')"))))); + } + + @Test + public void shouldOnlyCreateMessageSelectorForPostActionAndIgnoreAllOtherActions() throws Exception { + generator.run( + raml() + .with(resource() + .with(action(POST, "application/vnd.structure.commands.test-cmd1+json")) + .with(action(GET, "application/vnd.structure.commands.test-cmd2+json")) + .with(action(DELETE, "application/vnd.structure.commands.test-cmd3+json")) + .with(action(HEAD, "application/vnd.structure.commands.test-cmd4+json")) + .with(action(OPTIONS, "application/vnd.structure.commands.test-cmd5+json")) + .with(action(PATCH, "application/vnd.structure.commands.test-cmd6+json")) + .with(action(TRACE, "application/vnd.structure.commands.test-cmd7+json"))) + .build(), + configurationWithBasePackage(BASE_PACKAGE)); + + Class clazz = compiler.compiledClassOf(BASE_PACKAGE); + assertThat(clazz.getAnnotation(MessageDriven.class), is(notNullValue())); + assertThat(clazz.getAnnotation(MessageDriven.class).activationConfig(), + hasItemInArray(allOf(propertyName(equalTo("messageSelector")), + propertyValue(equalTo("CPPNAME in('structure.commands.test-cmd1')"))))); + } + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Test + public void shouldThrowExceptionIfNoActionsInRaml() throws Exception { + + exception.expect(JmsEndpointGeneratorException.class); + exception.expectMessage("No actions to process"); + + generator.run( + raml() + .with(resource() + .withRelativeUri("/structure.controller.commands")) + .build(), + configurationWithBasePackage(BASE_PACKAGE)); + } + + @Test + public void shouldCreateAnnotatedJmsEndpointWithMessageSelectorContainingTwoCommands() throws Exception { + generator.run( + raml() + .with(resource() + .with(action() + .with(ActionType.POST) + .withMediaType("application/vnd.people.commands.command1+json") + .withMediaType("application/vnd.people.commands.command2+json"))) + .build(), + configurationWithBasePackage(BASE_PACKAGE)); + + Class clazz = compiler.compiledClassOf(BASE_PACKAGE); + assertThat(clazz.getAnnotation(MessageDriven.class), is(notNullValue())); + assertThat(clazz.getAnnotation(MessageDriven.class).activationConfig(), + hasItemInArray(allOf(propertyName(equalTo("messageSelector")), + propertyValue(startsWith("CPPNAME in")), + propertyValue(allOf(containsString("'people.commands.command1'"), + containsString("'people.commands.command2'")))))); + } + + @Test + public void shouldCreateJmsEndpointWithDispatcherGetter() throws Exception { + generator.run(raml().withDefaults().build(), configurationWithBasePackage(BASE_PACKAGE)); + + Class clazz = compiler.compiledClassOf(BASE_PACKAGE); + + Object endpointInstance = clazz.newInstance(); + Dispatcher dispatcher = new DummyDispatcher(); + Field dispatcherField = clazz.getDeclaredField("dispatcher"); + dispatcherField.setAccessible(true); + dispatcherField.set(endpointInstance, dispatcher); + + Method getDispatcherMethod = clazz.getDeclaredMethod("getDispatcher"); + getDispatcherMethod.setAccessible(true); + + Object getDispatcherResult = getDispatcherMethod.invoke(endpointInstance); + assertThat(getDispatcherResult, sameInstance(dispatcher)); + } + + private GeneratorConfig configurationWithBasePackage(String basePackageName) { + Path outputPath = Paths.get(outputFolder.getRoot().getAbsolutePath()); + return new GeneratorConfig(outputPath, outputPath, basePackageName); + } + + private FeatureMatcher propertyName(Matcher matcher) { + return new FeatureMatcher(matcher, "propertyName", "propertyName") { + @Override + protected String featureValueOf(ActivationConfigProperty actual) { + return actual.propertyName(); + } + }; + } + + private FeatureMatcher propertyValue(Matcher matcher) { + return new FeatureMatcher(matcher, "propertyValue", "propertyValue") { + @Override + protected String featureValueOf(ActivationConfigProperty actual) { + return actual.propertyValue(); + } + }; + } + + public static class DummyDispatcher implements Dispatcher { + @Override + public void dispatch(Envelope envelope) { + // do nothing + } + } + +} diff --git a/adapters/jms-adapter-generator/src/test/java/uk/gov/justice/raml/jms/core/TemplateMarkerTest.java b/adapters/jms-adapter-generator/src/test/java/uk/gov/justice/raml/jms/core/TemplateMarkerTest.java new file mode 100644 index 000000000..5dcace15e --- /dev/null +++ b/adapters/jms-adapter-generator/src/test/java/uk/gov/justice/raml/jms/core/TemplateMarkerTest.java @@ -0,0 +1,54 @@ +package uk.gov.justice.raml.jms.core; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; + +public class TemplateMarkerTest { + + @Test + public void shouldReplaceMarkedPosition() throws Exception { + String template = "Test ${replace}"; + Map data = new HashMap<>(); + data.put("replace", "replacement"); + + String result = TemplateRenderer.render(template, data); + assertThat(result, equalTo("Test replacement")); + } + + @Test + public void shouldNotReplacePosition() throws Exception { + String template = "Test replace"; + Map data = new HashMap<>(); + data.put("replace", "replacement"); + + String result = TemplateRenderer.render(template, data); + assertThat(result, equalTo("Test replace")); + } + + @Test + public void shouldReplaceMultipleMarkedPositionsOfSameKey() throws Exception { + String template = "${replace} Test ${replace}"; + Map data = new HashMap<>(); + data.put("replace", "replacement"); + + String result = TemplateRenderer.render(template, data); + assertThat(result, equalTo("replacement Test replacement")); + } + + @Test + public void shouldReplaceMultipleMarkedPositionsOfDifferentKey() throws Exception { + String template = "${replace} Test ${different} ${replace} Test ${different}"; + Map data = new HashMap<>(); + data.put("replace", "replacement"); + data.put("different", "other"); + + String result = TemplateRenderer.render(template, data); + assertThat(result, equalTo("replacement Test other replacement Test other")); + } + +} diff --git a/adapters/jms-adapter-generator/src/test/resources/log4j.properties b/adapters/jms-adapter-generator/src/test/resources/log4j.properties new file mode 100644 index 000000000..6193d62fd --- /dev/null +++ b/adapters/jms-adapter-generator/src/test/resources/log4j.properties @@ -0,0 +1,9 @@ +# Set root logger level to DEBUG and its only appender to A1. +log4j.rootLogger=WARN, A1 + +# A1 is set to be a ConsoleAppender. +log4j.appender.A1=org.apache.log4j.ConsoleAppender + +# A1 uses PatternLayout. +log4j.appender.A1.layout=org.apache.log4j.PatternLayout +log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n \ No newline at end of file diff --git a/adapters/rest-adapter-core/pom.xml b/adapters/rest-adapter-core/pom.xml index 7499d3ebb..d7e50ed99 100644 --- a/adapters/rest-adapter-core/pom.xml +++ b/adapters/rest-adapter-core/pom.xml @@ -49,7 +49,6 @@ org.glassfish javax.json - 1.0.4 test diff --git a/adapters/rest-adapter-generator/src/test/java/uk/gov/justice/services/adapters/rest/generator/NamesTest.java b/adapters/rest-adapter-generator/src/test/java/uk/gov/justice/services/adapters/rest/generator/NamesTest.java new file mode 100644 index 000000000..af38586f0 --- /dev/null +++ b/adapters/rest-adapter-generator/src/test/java/uk/gov/justice/services/adapters/rest/generator/NamesTest.java @@ -0,0 +1,42 @@ +package uk.gov.justice.services.adapters.rest.generator; + +import org.junit.Test; +import org.raml.model.Action; +import org.raml.model.MimeType; +import org.raml.model.Resource; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +public class NamesTest { + + @Test + public void shouldConvertMimetypeToShortMimeTypeString() throws Exception { + String shortMimeType = Names.getShortMimeType(new MimeType("application/vnd.people.commands.create-user+json")); + assertThat(shortMimeType, is("vndPeopleCommandsCreateUserJson")); + } + + @Test + public void shouldBuildMimeTypeInfix() throws Exception { + String shortMimeType = Names.buildMimeTypeInfix(new MimeType("application/vnd.people.commands.create-user+json")); + assertThat(shortMimeType, is("VndPeopleCommandsCreateUserJson")); + } + + @Test + public void shouldBuildMethodResourceName() throws Exception { + String shortMimeType = Names.buildMimeTypeInfix(new MimeType("application/vnd.people.commands.create-user+json")); + assertThat(shortMimeType, is("VndPeopleCommandsCreateUserJson")); + } + + @Test + public void shouldBuildResourceMethodName() throws Exception { + Resource resource = new Resource(); + resource.setParentUri(""); + resource.setRelativeUri("test"); + Action action = new Action(); + action.setResource(resource); + String shortMimeType = Names.buildMimeTypeInfix(new MimeType("application/vnd.people.commands.create-user+json")); + assertThat(shortMimeType, is("VndPeopleCommandsCreateUserJson")); + } + +} \ No newline at end of file diff --git a/adapters/rest-adapter-generator/src/test/resources/log4j.properties b/adapters/rest-adapter-generator/src/test/resources/log4j.properties new file mode 100644 index 000000000..db8a1d801 --- /dev/null +++ b/adapters/rest-adapter-generator/src/test/resources/log4j.properties @@ -0,0 +1,9 @@ +# Set root logger level to DEBUG and its only appender to A1. +log4j.rootLogger=WARN, A1 + +# A1 is set to be a ConsoleAppender. +log4j.appender.A1=org.apache.log4j.ConsoleAppender + +# A1 uses PatternLayout. +log4j.appender.A1.layout=org.apache.log4j.PatternLayout +log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n \ No newline at end of file