Skip to content

Commit

Permalink
micronaut support for grails
Browse files Browse the repository at this point in the history
  • Loading branch information
musketyr committed Sep 27, 2018
1 parent 4388678 commit 41b6215
Show file tree
Hide file tree
Showing 16 changed files with 608 additions and 9 deletions.
58 changes: 58 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,61 @@ micronaut:
server:
port: 46054
----

== Micronaut Grails

Micronaut Grails package helps using Micronaut beans in the Grails application. There are two additional features which
cannot be found the official Spring support for Micronaut:

1. Micronaut beans' names defaults to lower-cased simple name of the class as expected by Grails
2. Ability to reuse existing properties declared by Grails - e.g. `grails.redis.port` can be injected as `@Value('${redis.port}')`


=== Instalation

[source,indent=0,options="nowrap"]
.Gradle Installation
----
repositories {
jcenter()
}
dependencies {
compileOnly "com.agorapulse:micronaut-grails:$micronautLibrariesVersion"
}
----

TIP: If you plan to reuse same library for Micronaut and Grails, you can declare the dependency as `compileOnly`.

=== Usage

First, create a Spring configuration class which will create the processor bean:


[source,java,indent=0,options="nowrap"]
----
package com.agorapulse.micronaut.grails.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.agorapulse.micronaut.grails.GrailsMicronautBeanProcessor;
@Configuration
public class GrailsConfig {
@Bean
public GrailsMicronautBeanProcessor widgetProcessor() {
new GrailsMicronautBeanProcessor(Widget.class, Prototype.class); // <1>
}
}
----
<1> List all classes of beans you want to include into Spring application context. Alternatively, you can declare the common annotation of these beans such as `@Prototype.

Second, create `spring.factories` descriptor which will automatically load the configuration once on classpath.

.spring.factories
----
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.agorapulse.micronaut.grails.example.GrailsConfig
----
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ subprojects {
testCompile "io.micronaut:inject-groovy"
testCompile "io.micronaut:inject-java"

testCompile("org.spockframework:spock-core:1.1-groovy-2.4") {
testCompile("org.spockframework:spock-core:$spockVersion") {
exclude group: "org.codehaus.groovy", module: "groovy-all"
}
testCompile 'cglib:cglib-nodep:3.2.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class PlanetDBService {
amazonDynamoDBClient.createTable(mapper.generateCreateTableRequest(Planet).withProvisionedThroughput(
new ProvisionedThroughput().withReadCapacityUnits(5).withWriteCapacityUnits(5)
))
} catch (AmazonClientException e) {
} catch (Exception e) {
e.printStackTrace()
}
}
Expand Down
9 changes: 5 additions & 4 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
gruVersion = 0.6.3
druVersion = 0.4.4
groovyVersion = 2.5.1
micronautVersion = 1.0.0.M3
gruVersion = 0.6.5
druVersion = 0.4.5
groovyVersion = 2.5.2
micronautVersion = 1.0.0.M4
spockVersion = 1.2-groovy-2.5
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public Collection<Cookie> values() {

Map<String, String> queryStringParameters = Optional.ofNullable(input.getQueryStringParameters()).orElseGet(Collections::emptyMap);
this.parameters = new SimpleHttpParameters(conversionService);
queryStringParameters.forEach(this.parameters::put);
queryStringParameters.forEach(this.parameters::add);
}

@Override
Expand Down
8 changes: 8 additions & 0 deletions micronaut-grails/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
dependencies {
compile "io.micronaut:spring"

testCompile 'org.springframework:spring-test:5.0.8.RELEASE'
testCompile("org.spockframework:spock-spring:$spockVersion") {
exclude group: "org.codehaus.groovy", module: "groovy-all"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright 2017-2018 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http:https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.agorapulse.micronaut.grails;

import io.micronaut.context.DefaultBeanContext;
import io.micronaut.context.Qualifier;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.inject.qualifiers.Qualifiers;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.List;

/**
* Adds Micronaut beans to a Grails' Spring application context. This processor will
* find all of the Micronaut beans of the specified types
* and add them as beans to the Spring application context.
*
* The Grails properties can be translated to Micronaut properties if required.
*
* @author jeffbrown
* @author musketyr
* @since 1.0
*/
public class GrailsMicronautBeanProcessor implements BeanFactoryPostProcessor, DisposableBean, EnvironmentAware {

private static final String MICRONAUT_BEAN_TYPE_PROPERTY_NAME = "micronautBeanType";
private static final String MICRONAUT_CONTEXT_PROPERTY_NAME = "micronautContext";
private static final String MICRONAUT_SINGLETON_PROPERTY_NAME = "micronautSingleton";

private DefaultBeanContext micronautContext;
private final List<Class<?>> micronautBeanQualifierTypes;
private final PropertyTranslatingCustomizer customizer;
private Environment environment;

/**
* @param customizer properties translation customizer
* @param qualifierTypes The types associated with the
* Micronaut beans which should be added to the
* Spring application context.
*/
public GrailsMicronautBeanProcessor(PropertyTranslatingCustomizer customizer, Class<?>... qualifierTypes) {
this.customizer = customizer;
this.micronautBeanQualifierTypes = Arrays.asList(qualifierTypes);
}

public GrailsMicronautBeanProcessor(Class<?>... qualifierTypes) {
this(PropertyTranslatingCustomizer.grails().build(), qualifierTypes);
}

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (environment == null) {
throw new IllegalStateException("Spring environment not set!");
}

micronautContext = new GrailsPropertyTranslatingApplicationContext(environment, customizer);

micronautContext.start();

micronautBeanQualifierTypes
.forEach(micronautBeanQualifierType -> {
Qualifier<Object> micronautBeanQualifier;
if (micronautBeanQualifierType.isAnnotation()) {
micronautBeanQualifier = Qualifiers.byStereotype((Class<? extends Annotation>) micronautBeanQualifierType);
} else {
micronautBeanQualifier = Qualifiers.byType(micronautBeanQualifierType);
}
micronautContext.getBeanDefinitions(micronautBeanQualifier)
.forEach(definition -> {
final BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.rootBeanDefinition("io.micronaut.spring.beans.MicronautSpringBeanFactory");
beanDefinitionBuilder.addPropertyValue(MICRONAUT_BEAN_TYPE_PROPERTY_NAME, definition.getBeanType());
beanDefinitionBuilder.addPropertyValue(MICRONAUT_CONTEXT_PROPERTY_NAME, micronautContext);
beanDefinitionBuilder.addPropertyValue(MICRONAUT_SINGLETON_PROPERTY_NAME, definition.isSingleton());
String name = definition.getName();
if (name.equals(definition.getBeanType().getName())) {
name = NameUtils.decapitalize(definition.getBeanType().getSimpleName());
}
((DefaultListableBeanFactory) beanFactory).registerBeanDefinition(name, beanDefinitionBuilder.getBeanDefinition());
});
});
}

@Override
public void destroy() {
if (micronautContext != null) {
micronautContext.close();
}
}

@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}

/**
* Returns the property name representation of the given name.
*
* Ported from grails.util.GrailsNameUtils
*
* @param name The name to convert
* @return The property name representation
*/

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.agorapulse.micronaut.grails;

import io.micronaut.context.DefaultApplicationContext;
import io.micronaut.context.env.Environment;

class GrailsPropertyTranslatingApplicationContext extends DefaultApplicationContext {

private final Environment environment;

GrailsPropertyTranslatingApplicationContext(org.springframework.core.env.Environment environment, PropertyTranslatingCustomizer customizer) {
super(environment.getActiveProfiles());
this.environment = new GrailsPropertyTranslatingEnvironment(environment, customizer);
}

@Override
public io.micronaut.context.env.Environment getEnvironment() {
return environment;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.agorapulse.micronaut.grails;

import java.util.*;
import java.util.regex.Pattern;

class GrailsPropertyTranslatingCustomizer implements PropertyTranslatingCustomizer, PropertyTranslatingCustomizer.Builder {

private static class PrefixReplacement {
final String original;
final String replacement;

PrefixReplacement(String original, String replacement) {
this.original = original;
this.replacement = replacement;
}

}

private static final String GRAILS_PREFIX = "grails.";
private static final String MICRONAUT_PREFIX = "micronaut.";
private static final String EMPTY_STRING = "";

static GrailsPropertyTranslatingCustomizer create() {
return new GrailsPropertyTranslatingCustomizer();
}

static GrailsPropertyTranslatingCustomizer standard() {
return new GrailsPropertyTranslatingCustomizer()
.replacePrefix(MICRONAUT_PREFIX, GRAILS_PREFIX)
.replacePrefix(EMPTY_STRING, GRAILS_PREFIX);
}

private final List<PrefixReplacement> prefixPrefixReplacements = new ArrayList<>();
private final Set<Pattern> ignored = new LinkedHashSet<>();

private GrailsPropertyTranslatingCustomizer() {
// prevents instantiation
}

/**
* Replace property name prefixes.
* @param original original prefix, can be empty
* @param replacement new prefix, can be empty
* @return self
*/
@Override
public GrailsPropertyTranslatingCustomizer replacePrefix(String original, String replacement) {
prefixPrefixReplacements.add(new PrefixReplacement(original, replacement));
return this;
}

/**
* Ignores exact match of the property name.
* @param property the property to be ignored
* @return self
*/
@Override
public GrailsPropertyTranslatingCustomizer ignore(String property) {
ignored.add(Pattern.compile(Pattern.quote(property)));
return this;
}

/**
* Ignores by regex match of the property pattern.
* @param propertyPattern the pattern of properties being ignored
* @return self
*/
@Override
public GrailsPropertyTranslatingCustomizer ignoreAll(String propertyPattern) {
ignored.add(Pattern.compile(propertyPattern));
return this;
}

@Override
public PropertyTranslatingCustomizer build() {
return this;
}

@Override
public Set<String> getAlternativeNames(String name) {
if (name == null || name.length() == 0) {
return Collections.emptySet();
}

if (ignored.stream().anyMatch(p -> p.matcher(name).matches())) {
return Collections.emptySet();
}

Set<String> keys = new LinkedHashSet<>(prefixPrefixReplacements.size());
prefixPrefixReplacements.forEach(replacement -> {
if ((replacement.original.isEmpty() || name.startsWith(replacement.original)) && (replacement.replacement.isEmpty() || !name.startsWith(replacement.replacement))) {
String keyNoPrefix = name.substring(replacement.original.length());
String alternativeKey = replacement.replacement + keyNoPrefix;
if (!alternativeKey.equals(name)) {
keys.add(alternativeKey);
}
}
});
return keys;
}

}
Loading

0 comments on commit 41b6215

Please sign in to comment.