Java tutorial
/* * Copyright 2013-2017 (c) MuleSoft, Inc. * * 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 org.raml.jaxrs.generator.builders.resources; import com.google.common.base.Function; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; import org.raml.jaxrs.generator.CurrentBuild; import org.raml.jaxrs.generator.GenerationException; import org.raml.jaxrs.generator.HTTPMethods; import org.raml.jaxrs.generator.Names; import org.raml.jaxrs.generator.ResourceUtils; import org.raml.jaxrs.generator.builders.CodeContainer; import org.raml.jaxrs.generator.builders.JavaPoetTypeGeneratorBase; import org.raml.jaxrs.generator.builders.extensions.resources.ResourceContextImpl; import org.raml.jaxrs.generator.extension.resources.ResourceClassExtension; import org.raml.jaxrs.generator.extension.resources.ResourceContext; import org.raml.jaxrs.generator.ramltypes.GMethod; import org.raml.jaxrs.generator.ramltypes.GParameter; import org.raml.jaxrs.generator.ramltypes.GRequest; import org.raml.jaxrs.generator.ramltypes.GResource; import org.raml.jaxrs.generator.ramltypes.GResponse; import org.raml.jaxrs.generator.ramltypes.GResponseType; import org.raml.jaxrs.generator.ramltypes.GType; import org.raml.jaxrs.generator.v10.Annotations; import org.raml.jaxrs.generator.v10.TypeUtils; import javax.annotation.Nullable; import javax.lang.model.element.Modifier; import javax.ws.rs.Consumes; import javax.ws.rs.HttpMethod; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.GenericEntity; import javax.ws.rs.core.Response; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Created by Jean-Philippe Belanger on 10/27/16. Abstraction of creation. */ public class ResourceBuilder implements ResourceGenerator { private final CurrentBuild build; private final GResource topResource; private final String name; private final String uri; public ResourceBuilder(CurrentBuild build, GResource resource, String name, String uri) { this.build = build; this.topResource = resource; this.name = name; this.uri = uri; } @Override public void output(CodeContainer<TypeSpec> container) throws IOException { TypeSpec.Builder typeSpec = build .getResourceClassExtension(new DefaultResourceClassCreator(), Annotations.ON_RESOURCE_CLASS_CREATION, topResource) .onResource(new ResourceContextImpl(build), topResource, null); if (typeSpec != null) { container.into(typeSpec.build()); } } private void recurse(TypeSpec.Builder typeSpec, GResource parentResource) { for (GResource resource : parentResource.resources()) { buildResource(typeSpec, resource); recurse(typeSpec, resource); } } private void buildResource(TypeSpec.Builder typeSpec, GResource currentResource) { Multimap<GMethod, GRequest> incomingBodies = ArrayListMultimap.create(); Multimap<GMethod, GResponse> responses = ArrayListMultimap.create(); ResourceUtils.fillInBodiesAndResponses(currentResource, incomingBodies, responses); Map<String, TypeSpec.Builder> responseSpecs = createResponseClass(typeSpec, incomingBodies, responses); for (GMethod gMethod : currentResource.methods()) { String methodName = Names.resourceMethodName(gMethod.resource(), gMethod); Set<String> mediaTypesForMethod = fetchAllMediaTypesForMethodResponses(gMethod); if (gMethod.body().size() == 0) { createMethodWithoutBody(typeSpec, gMethod, mediaTypesForMethod, methodName, responseSpecs); } else { Multimap<String, String> ramlTypeToMediaType = accumulateMediaTypesPerType(incomingBodies, gMethod); for (GRequest gRequest : gMethod.body()) { if (ramlTypeToMediaType.containsKey(gRequest.type().name())) { createMethodWithBody(typeSpec, gMethod, ramlTypeToMediaType, methodName, gRequest, responseSpecs); ramlTypeToMediaType.removeAll(gRequest.type().name()); } } } } } private Multimap<String, String> accumulateMediaTypesPerType(Multimap<GMethod, GRequest> incomingBodies, GMethod gMethod) { Multimap<String, String> ramlTypeToMediaType = ArrayListMultimap.create(); for (GRequest request : incomingBodies.get(gMethod)) { if (request != null) { ramlTypeToMediaType.put(request.type().name(), request.mediaType()); } } return ramlTypeToMediaType; } private void createMethodWithoutBody(TypeSpec.Builder typeSpec, GMethod gMethod, Set<String> mediaTypesForMethod, String methodName, Map<String, TypeSpec.Builder> responseSpecs) { MethodSpec.Builder methodSpec = createMethodBuilder(gMethod, methodName, mediaTypesForMethod, responseSpecs); // here I would run my plugins.... methodSpec = build.getResourceMethodExtension(Annotations.ON_METHOD_FINISH, gMethod) .onMethod(new ResourceContextImpl(build), gMethod, methodSpec); if (methodSpec != null) { typeSpec.addMethod(methodSpec.build()); } } private void createMethodWithBody(TypeSpec.Builder typeSpec, GMethod gMethod, Multimap<String, String> ramlTypeToMediaType, String methodName, GRequest gRequest, Map<String, TypeSpec.Builder> responseSpec) { MethodSpec.Builder methodSpec = createMethodBuilder(gMethod, methodName, new HashSet<String>(), responseSpec); TypeName name = gRequest.type().defaultJavaTypeName(build.getModelPackage()); methodSpec.addParameter(ParameterSpec.builder(name, "entity").build()); handleMethodConsumer(methodSpec, ramlTypeToMediaType, gRequest.type()); methodSpec = build.getResourceMethodExtension(Annotations.ON_METHOD_FINISH, gMethod) .onMethod(new ResourceContextImpl(build), gMethod, methodSpec); if (methodSpec != null) { typeSpec.addMethod(methodSpec.build()); } } private Set<String> fetchAllMediaTypesForMethodResponses(GMethod gMethod) { Set<String> mediaTypes = new HashSet<>(); for (GResponse gResponse : gMethod.responses()) { mediaTypes.addAll(Lists.transform(gResponse.body(), new Function<GResponseType, String>() { @Nullable @Override public String apply(@Nullable GResponseType input) { return input.mediaType(); } })); } return mediaTypes; } private Map<String, TypeSpec.Builder> createResponseClass(TypeSpec.Builder typeSpec, Multimap<GMethod, GRequest> bodies, Multimap<GMethod, GResponse> responses) { Map<String, TypeSpec.Builder> map = new HashMap<>(); Set<GMethod> allMethods = new HashSet<>(); allMethods.addAll(bodies.keySet()); allMethods.addAll(responses.keySet()); for (GMethod gMethod : allMethods) { if (gMethod.responses().size() == 0) { continue; } String defaultName = Names.responseClassName(gMethod.resource(), gMethod); TypeSpec.Builder responseClass = TypeSpec.classBuilder(defaultName) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .superclass(ClassName.get(build.getSupportPackage(), "ResponseDelegate")) .addMethod(MethodSpec.constructorBuilder() .addParameter(javax.ws.rs.core.Response.class, "response") .addParameter(Object.class, "entity").addModifiers(Modifier.PRIVATE) .addCode("super(response, entity);\n").build()) .addMethod(MethodSpec.constructorBuilder() .addParameter(javax.ws.rs.core.Response.class, "response") .addModifiers(Modifier.PRIVATE).addCode("super(response);\n").build()); ; responseClass = build.getResponseClassExtension(Annotations.ON_RESPONSE_CLASS_CREATION, gMethod) .onMethod(new ResourceContextImpl(build), gMethod, responseClass); if (responseClass == null) { map.put(defaultName, null); continue; } TypeSpec currentClass = responseClass.build(); for (GResponse gResponse : responses.get(gMethod)) { if (gResponse == null) { continue; } if (gResponse.body().size() == 0) { String httpCode = gResponse.code(); MethodSpec.Builder builder = MethodSpec.methodBuilder("respond" + httpCode); builder.addModifiers(Modifier.STATIC, Modifier.PUBLIC) .addStatement( "Response.ResponseBuilder responseBuilder = Response.status(" + httpCode + ")") .addStatement("return new $N(responseBuilder.build())", currentClass) .returns(TypeVariableName.get(currentClass.name)).build(); builder = build.getResponseMethodExtension(Annotations.ON_RESPONSE_METHOD_CREATION, gResponse) .onMethod(new ResourceContextImpl(build), gResponse, builder); if (builder == null) { continue; } builder = build.getResponseMethodExtension(Annotations.ON_RESPONSE_METHOD_FINISH, gResponse) .onMethod(new ResourceContextImpl(build), gResponse, builder); if (builder == null) { continue; } responseClass.addMethod(builder.build()); } else { for (GResponseType typeDeclaration : gResponse.body()) { String httpCode = gResponse.code(); MethodSpec.Builder builder = MethodSpec .methodBuilder( Names.methodName("respond", httpCode, "With", typeDeclaration.mediaType())) .addModifiers(Modifier.STATIC, Modifier.PUBLIC); builder = build .getResponseMethodExtension(Annotations.ON_RESPONSE_METHOD_CREATION, gResponse) .onMethod(new ResourceContextImpl(build), gResponse, builder); if (builder == null) { continue; } builder.returns(TypeVariableName.get(currentClass.name)); TypeName typeName = typeDeclaration.type().defaultJavaTypeName(build.getModelPackage()); if (typeName == null) { throw new GenerationException(typeDeclaration + " was not seen before"); } builder.addParameter(ParameterSpec.builder(typeName, "entity").build()); builder.addStatement("Response.ResponseBuilder responseBuilder = Response.status(" + httpCode + ").header(\"Content-Type\", \"" + typeDeclaration.mediaType() + "\")"); if (typeDeclaration.type().isArray() && typeDeclaration.mediaType().contains("xml")) { builder.addStatement("$T<$T> wrappedEntity = new $T<$T>(entity){}", GenericEntity.class, typeName, GenericEntity.class, typeName) .addStatement("responseBuilder.entity(wrappedEntity)").addStatement( "return new $N(responseBuilder.build(), wrappedEntity)", currentClass); } else { builder.addStatement("responseBuilder.entity(entity)") .addStatement("return new $N(responseBuilder.build(), entity)", currentClass); } builder = build.getResponseMethodExtension(Annotations.ON_RESPONSE_METHOD_FINISH, gResponse) .onMethod(new ResourceContextImpl(build), gResponse, builder); if (builder == null) { continue; } responseClass.addMethod(builder.build()); } } } responseClass = build.getResponseClassExtension(Annotations.ON_RESPONSE_CLASS_FINISH, gMethod) .onMethod(new ResourceContextImpl(build), gMethod, responseClass); if (responseClass == null) { map.put(defaultName, null); continue; } map.put(defaultName, responseClass); typeSpec.addType(responseClass.build()); } return map; } private MethodSpec.Builder createMethodBuilder(GMethod gMethod, String methodName, Set<String> mediaTypesForMethod, Map<String, TypeSpec.Builder> responseSpec) { MethodSpec.Builder methodSpec = MethodSpec.methodBuilder(methodName).addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC); methodSpec = build.getResourceMethodExtension(Annotations.ON_METHOD_CREATION, gMethod) .onMethod(new ResourceContextImpl(build), gMethod, methodSpec); for (GParameter typeDeclaration : gMethod.resource().uriParameters()) { if (TypeUtils.isComposite(typeDeclaration)) { throw new GenerationException("uri parameter is composite: " + typeDeclaration); } methodSpec.addParameter(ParameterSpec .builder(typeDeclaration.type().defaultJavaTypeName(build.getModelPackage()), Names.methodName(typeDeclaration.name())) .addAnnotation(AnnotationSpec.builder(PathParam.class) .addMember("value", "$S", typeDeclaration.name()).build()) .build()); } for (GParameter typeDeclaration : gMethod.queryParameters()) { if (TypeUtils.isComposite(typeDeclaration)) { throw new GenerationException("query parameter is composite: " + typeDeclaration); } methodSpec.addParameter(ParameterSpec .builder(typeDeclaration.type().defaultJavaTypeName(build.getModelPackage()), Names.methodName(typeDeclaration.name())) .addAnnotation(AnnotationSpec.builder(QueryParam.class) .addMember("value", "$S", typeDeclaration.name()).build()) .build()); } buildNewWebMethod(gMethod, methodSpec); if (gMethod.resource().parentResource() != null) { methodSpec.addAnnotation(AnnotationSpec.builder(Path.class) .addMember("value", "$S", gMethod.resource().relativePath()).build()); } if (gMethod.responses().size() != 0) { TypeSpec.Builder responseSpecForMethod = responseSpec .get(Names.responseClassName(gMethod.resource(), gMethod)); if (responseSpecForMethod == null) { methodSpec.returns(ClassName.get(Response.class)); } else { methodSpec.returns(ClassName.get("", responseSpecForMethod.build().name)); } } else { methodSpec.returns(ClassName.VOID); } if (mediaTypesForMethod.size() > 0) { AnnotationSpec.Builder ann = buildAnnotation(mediaTypesForMethod, Produces.class); methodSpec.addAnnotation(ann.build()); } return methodSpec; } private void buildNewWebMethod(GMethod gMethod, MethodSpec.Builder methodSpec) { Class<? extends Annotation> type = HTTPMethods.methodNameToAnnotation(gMethod.method()); if (type == null) { String name = gMethod.method().toUpperCase(); final ClassName className = ClassName.get(build.getSupportPackage(), name); final TypeSpec.Builder builder = TypeSpec.annotationBuilder(className); builder.addModifiers(Modifier.PUBLIC) .addAnnotation(AnnotationSpec.builder(Target.class) .addMember("value", "{$T.$L}", ElementType.class, "METHOD").build()) .addAnnotation(AnnotationSpec.builder(Retention.class) .addMember("value", "$T.$L", RetentionPolicy.class, "RUNTIME").build()) .addAnnotation(AnnotationSpec.builder(HttpMethod.class).addMember("value", "$S", name).build()); build.newSupportGenerator(new JavaPoetTypeGeneratorBase(className) { @Override public void output(CodeContainer<TypeSpec.Builder> rootDirectory) throws IOException { rootDirectory.into(builder); } }); methodSpec.addAnnotation(AnnotationSpec.builder(className).build()); } else { methodSpec.addAnnotation(AnnotationSpec.builder(type).build()); } } private void handleMethodConsumer(MethodSpec.Builder methodSpec, Multimap<String, String> ramlTypeToMediaType, GType typeDeclaration) { Collection<String> mediaTypes = ramlTypeToMediaType.get(typeDeclaration.name()); AnnotationSpec.Builder ann = buildAnnotation(mediaTypes, Consumes.class); methodSpec.addAnnotation(ann.build()); } private AnnotationSpec.Builder buildAnnotation(Collection<String> mediaTypes, Class<? extends Annotation> type) { AnnotationSpec.Builder ann = AnnotationSpec.builder(type); for (String mediaType : mediaTypes) { ann.addMember("value", "$S", mediaType); } return ann; } private class DefaultResourceClassCreator implements ResourceClassExtension<GResource> { @Override public TypeSpec.Builder onResource(ResourceContext context, GResource resource, TypeSpec.Builder nullSpec) { TypeSpec.Builder typeSpec = TypeSpec.interfaceBuilder(Names.typeName(name)) .addModifiers(Modifier.PUBLIC) .addAnnotation(AnnotationSpec.builder(Path.class).addMember("value", "$S", uri).build()); buildResource(typeSpec, topResource); recurse(typeSpec, topResource); typeSpec = build .getResourceClassExtension(NULL_EXTENSION, Annotations.ON_RESOURCE_CLASS_FINISH, topResource) .onResource(new ResourceContextImpl(build), topResource, typeSpec); return typeSpec; } } }