Skip to content

Commit

Permalink
Views rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
sdelamo committed Aug 8, 2018
1 parent e686041 commit 412afa4
Show file tree
Hide file tree
Showing 43 changed files with 2,246 additions and 7 deletions.
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ testsredis=configurations/redis-lettuce/src/test/groovy/io/micronaut/configurati
testsfunctionclient=function-client/src/test/groovy/io/micronaut/function/client/aws
testsmetricscore=configurations/micrometer-core/src/test/groovy/io/micronaut/docs
testsvalidation=validation/src/test/groovy/io/micronaut/docs
testsviews=views/src/test/groovy/io/micronaut/docs
metricscore=configurations/micrometer-core/src/main/java/io/micronaut/configuration/metrics
examples=examples
jdkapi=https://docs.oracle.com/javase/8/docs/api
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1001,19 +1001,19 @@ private void subscribeToResponsePublisher(
Optional<MediaTypeCodec> registeredCodec = mediaTypeCodecRegistry.findCodec(responseMediaType, body.getClass());
if (registeredCodec.isPresent()) {
MediaTypeCodec codec = registeredCodec.get();
return encodeBodyWithCodec(response, body, codec, responseMediaType, context);
return encodeBodyWithCodec(response, body, codec, responseMediaType, context, requestReference);
}
}

Optional<MediaTypeCodec> registeredCodec = mediaTypeCodecRegistry.findCodec(defaultResponseMediaType, body.getClass());
if (registeredCodec.isPresent()) {
MediaTypeCodec codec = registeredCodec.get();
return encodeBodyWithCodec(response, body, codec, responseMediaType, context);
return encodeBodyWithCodec(response, body, codec, responseMediaType, context, requestReference);
}

MediaTypeCodec defaultCodec = new TextPlainCodec(serverConfiguration.getDefaultCharset());

return encodeBodyWithCodec(response, body, defaultCodec, responseMediaType, context);
return encodeBodyWithCodec(response, body, defaultCodec, responseMediaType, context, requestReference);
} else {
return response;
}
Expand Down Expand Up @@ -1065,8 +1065,13 @@ private void writeFinalNettyResponse(MutableHttpResponse<?> message, AtomicRefer
}
}

private MutableHttpResponse<?> encodeBodyWithCodec(MutableHttpResponse<?> response, Object body, MediaTypeCodec codec, MediaType mediaType, ChannelHandlerContext context) {
ByteBuf byteBuf = encodeBodyAsByteBuf(body, codec, context);
private MutableHttpResponse<?> encodeBodyWithCodec(MutableHttpResponse<?> response,
Object body,
MediaTypeCodec codec,
MediaType mediaType,
ChannelHandlerContext context,
AtomicReference<HttpRequest<?>> requestReference) {
ByteBuf byteBuf = encodeBodyAsByteBuf(body, codec, context, requestReference);
int len = byteBuf.readableBytes();
MutableHttpHeaders headers = response.getHeaders();
if (!headers.contains(HttpHeaders.CONTENT_TYPE)) {
Expand All @@ -1085,7 +1090,7 @@ private MutableHttpResponse<?> setBodyContent(MutableHttpResponse response, Obje
return res;
}

private ByteBuf encodeBodyAsByteBuf(Object body, MediaTypeCodec codec, ChannelHandlerContext context) {
private ByteBuf encodeBodyAsByteBuf(Object body, MediaTypeCodec codec, ChannelHandlerContext context, AtomicReference<HttpRequest<?>> requestReference) {
ByteBuf byteBuf;
if (body instanceof ByteBuf) {
byteBuf = (ByteBuf) body;
Expand All @@ -1099,6 +1104,19 @@ private ByteBuf encodeBodyAsByteBuf(Object body, MediaTypeCodec codec, ChannelHa
}
} else if (body instanceof byte[]) {
byteBuf = Unpooled.copiedBuffer((byte[]) body);

} else if (body instanceof Writable) {
byteBuf = context.alloc().ioBuffer(128);
ByteBufOutputStream outputStream = new ByteBufOutputStream(byteBuf);
Writable writable = (Writable) body;
try {
writable.writeTo(outputStream, requestReference.get().getCharacterEncoding());
} catch (IOException e) {
if (LOG.isErrorEnabled()) {
LOG.error(e.getMessage());
}
}

} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Encoding emitted response object [{}] using codec: {}", body, codec);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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://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 io.micronaut.web.router.qualifier;

import io.micronaut.context.Qualifier;
import io.micronaut.http.HttpHeaders;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Produces;
import io.micronaut.inject.BeanType;

import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* A {@link io.micronaut.context.annotation.Bean} {@link Qualifier} that qualifies based on the value of the media type
* defined in the {@link Produces} annotation.
*
* @param <T> The Type
* @author Sergio del Amo
* @since 1.0
*/
public class ProducesMediaTypeQualifier<T> implements Qualifier<T> {

private final MediaType contentType;

/**
* @param contentType The content type as {@link MediaType}
*/
public ProducesMediaTypeQualifier(MediaType contentType) {
this.contentType = contentType;
}

@Override
public <BT extends BeanType<T>> Stream<BT> reduce(Class<T> beanType, Stream<BT> candidates) {
return candidates.filter(candidate -> {
Optional<MediaType[]> consumes = candidate.getAnnotationMetadata().getValue(Produces.class, MediaType[].class);
if (consumes.isPresent()) {
Set<String> consumedTypes = Arrays.stream(consumes.get()).map(MediaType::getExtension).collect(Collectors.toSet());
return consumedTypes.contains(contentType.getExtension());
}
return false;
}
);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

ProducesMediaTypeQualifier that = (ProducesMediaTypeQualifier) o;

return contentType.equals(that.contentType);
}

@Override
public int hashCode() {
return contentType.hashCode();
}

@Override
public String toString() {
return HttpHeaders.CONTENT_TYPE + ": " + contentType;
}
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ include "bom"
include "security"
include "security-jwt"
include "security-session"
include "views"

// configurations
include "configurations:gorm-common"
Expand Down
56 changes: 56 additions & 0 deletions src/main/docs/guide/httpServer/views.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
To use the templating features described in this section, add the dependency on your classpath. For example, in `build.gradle`

.build.gradle
[source,groovy]
----
compile "io.micronaut:views"
----

Place your templates at `src/main/resource/views`.

If you wish to use a different folder instead of `views`, set the property `micronaut.views.folder`.

Your controller's method can render the response with a template with the the api:views.View[] annotation.

The following is an example of a controller which renders a template.

[source,java]
.src/main/java/myapp/ViewsController.java
----
include::{testsviews}/ViewsController.groovy[tags=clazz]
include::{testsviews}/ViewsController.groovy[tags=map]
}
----

<1> Use `@View` annotation to indicate the template name which should be used to render a view for the route.

Moreover, you can render a POJO with a template to:

[source,java]
.src/main/java/myapp/ViewsController.java
----
include::{testsviews}/ViewsController.groovy[tags=clazz]
include::{testsviews}/ViewsController.groovy[tags=pogo]
}
----

<1> Use `@View` annotation to indicate the template name which should be used to render the POJO responded by the controller.

You can also return a api:views.ModelAndView[] and skip specifying the api:views.View[] annotation.

[source,java]
.src/main/java/myapp/ViewsController.java
----
include::{testsviews}/ViewsController.groovy[tags=clazz]
include::{testsviews}/ViewsController.groovy[tags=modelAndView]
----

The next sections show different template engines integrations.

To create your own implementation create a class which implements api:views.ViewRenderer[] and annotate it with api:http.annotation.Produces[@Produces] to indicate
the media type produced.
17 changes: 17 additions & 0 deletions src/main/docs/guide/httpServer/views/handlebars.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Micronaut includes api:views.handlebars.HandlebarsViewsRenderer[] which uses
the http://jknack.github.io/handlebars.java/[Handlebars.java].

In addition to the <<views, Views>> dependency, add the following dependency on your classpath. For example, in `build.gradle`

[source, groovy]
----
runtime "com.github.jknack:handlebars:4.1.0"
----

The example shown in the <<views, Views>> section, could be rendered with the following Handlebars template:

[source,html]
.src/main/resources/views/home.hbs
----
include::{testsviews}/../../../../resources/views/home.hbs[]
----
30 changes: 30 additions & 0 deletions src/main/docs/guide/httpServer/views/thymeleaf.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Micronaut includes api:views.thymeleaf.ThymeleafViewsRenderer[] which uses
the https://www.thymeleaf.org[Thymeleaf] Java template engine.

In addition to the <<views, Views>> dependency, add the following dependency on your classpath. For example, in `build.gradle`

[source, groovy]
----
runtime "org.thymeleaf:thymeleaf:3.0.9.RELEASE"
----

Thymeleaf integration instantiates a `ClassLoaderTemplateResolver`.
The properties used can be customized by overriding the values of api:io.micronaut.views.ThymeleafConfigurationProperties[].

The example shown in the <<views, Views>> section, could be rendered with the following Thymeleaf template:

[source,html]
.src/main/resources/views/home.html
----
include::{testsviews}/../../../../resources/views/home.html[]
}
----

and layout:

[source,html]
.src/main/resources/views/layoutFile.html
----
include::{testsviews}/../../../../resources/views/layoutFile.html[]
}
----
17 changes: 17 additions & 0 deletions src/main/docs/guide/httpServer/views/velocity.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Micronaut includes api:views.velocity.VelocityViewsRenderer[] which uses
the http://velocity.apache.org[Apache Velocity] Java-based template engine.

In addition to the <<views, Views>> dependency, add the following dependency on your classpath. For example, in `build.gradle`

[source, groovy]
----
runtime "org.apache.velocity:velocity-engine-core:2.0"
----

The example shown in the <<views, Views>> section, could be rendered with the following Velocity template:

[source,html]
.src/main/resources/views/home.vm
----
include::{testsviews}/../../../../resources/views/home.vm[]
----
7 changes: 6 additions & 1 deletion src/main/docs/guide/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@ httpServer:
title: Configuring the HTTP Server
threadPools: Configuring Server Thread Pools
cors: Configuring CORS
https: Securing the Server with HTTPS
https: Securing the Server with HTTPS
views:
title: Views
thymeleaf: Thymeleaf
handlebars: Handlebars.java
velocity: Apache Velocity
httpClient:
title: The HTTP Client
lowLevelHttpClient:
Expand Down
25 changes: 25 additions & 0 deletions views/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
ext {
snakeYamlVersion = '1.19'
velocityEngine = '2.0'
handlebarsVersion='4.1.0'
thymeleafVersion='3.0.9.RELEASE'
}

dependencies {
compileOnly project(":inject-java")
compileOnly "org.apache.velocity:velocity-engine-core:$velocityEngine"
compileOnly "com.github.jknack:handlebars:${handlebarsVersion}"
compileOnly "org.thymeleaf:thymeleaf:$thymeleafVersion"

compile project(":runtime")
compile project(":http")
compile project(":http-server")

testCompile project(":http-client")
testCompile project(":inject-groovy")
testCompile project(":http-server-netty")
testCompile "org.yaml:snakeyaml:$snakeYamlVersion"
testRuntime "org.apache.velocity:velocity-engine-core:$velocityEngine"
testRuntime "com.github.jknack:handlebars:${handlebarsVersion}"
testRuntime "org.thymeleaf:thymeleaf:$thymeleafVersion"
}
Loading

0 comments on commit 412afa4

Please sign in to comment.