Java tutorial
/* * Copyright 2015 Flipkart Internet, pvt ltd. * * 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 com.flipkart.poseidon.serviceclients.generator; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.flipkart.poseidon.model.annotations.Description; import com.flipkart.poseidon.model.annotations.Name; import com.flipkart.poseidon.model.annotations.Version; import com.flipkart.poseidon.serviceclients.AbstractServiceClient; import com.flipkart.poseidon.serviceclients.FutureTaskResultToDomainObjectPromiseWrapper; import com.flipkart.poseidon.serviceclients.ServiceExecutePropertiesBuilder; import com.flipkart.poseidon.serviceclients.idl.pojo.EndPoint; import com.flipkart.poseidon.serviceclients.idl.pojo.Parameter; import com.flipkart.poseidon.serviceclients.idl.pojo.ServiceIDL; import com.google.common.base.Joiner; import com.sun.codemodel.*; import flipkart.lego.api.entities.ServiceClient; import org.apache.commons.lang.Validate; import org.apache.commons.lang3.StringUtils; import javax.validation.constraints.NotNull; import java.beans.Introspector; import java.io.File; import java.io.PrintStream; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.flipkart.poseidon.helper.CallableNameHelper.canonicalName; /** * Created by mohan.pandian on 20/02/15. * * Uses sun's codemodel to generate service clients java interfaces and default implementations. * This is where the core business logic of service client generator lies */ public class ServiceGenerator { private static final ServiceGenerator SERVICE_GENERATOR = new ServiceGenerator(); private static final Pattern PARAMETERS_PATTERN = Pattern.compile("\\{parameters\\.(.*?)\\}"); private static final String REQUEST_OBJECT_VAR_NAME = "requestObject"; private static final String REQUEST_OBJECT_LOOP_VAR_NAME = "requestObject1"; private ServiceGenerator() { } public static ServiceGenerator getInstance() { return SERVICE_GENERATOR; } private String getInterfaceName(ServiceIDL serviceIdl) { return serviceIdl.getService().getName() + "Client"; } private String getFullInterfaceName(ServiceIDL serviceIdl) { return serviceIdl.getService().getPackageName() + "." + getInterfaceName(serviceIdl); } private String getImplName(ServiceIDL serviceIdl) { return serviceIdl.getService().getName() + "ClientImpl"; } private String getFullImplName(ServiceIDL serviceIdl) { return serviceIdl.getService().getPackageName() + "." + getImplName(serviceIdl); } private JType getJType(JCodeModel jCodeModel, String name) { if (name == null || name.length() == 0) { name = "void"; } try { return jCodeModel.parseType(name); } catch (ClassNotFoundException e) { return jCodeModel.directClass(name); } } private Map<String, String> getAllHeaders(ServiceIDL serviceIdl, EndPoint endPoint) { Map<String, String> headersMap = new HashMap<>(); if (serviceIdl.getService().getHeaders() != null) headersMap.putAll(serviceIdl.getService().getHeaders()); if (endPoint.getHeaders() != null) headersMap.putAll(endPoint.getHeaders()); return headersMap; } private void generateMethodParam(ServiceIDL serviceIdl, JCodeModel jCodeModel, JMethod method, JDocComment methodComment, String paramName) { Parameter parameter = serviceIdl.getParameters().get(paramName); JType jType = getJType(jCodeModel, parameter.getType()); if (!parameter.getOptional()) { method.param(jType.boxify(), paramName).annotate(NotNull.class); } else { method.param(jType.boxify(), paramName); } JCommentPart paramComment = methodComment.addParam(paramName); if (parameter.getDescription() != null) { for (String description : parameter.getDescription()) { paramComment.append(description); } } } private void addClassComments(ServiceIDL serviceIdl, JDefinedClass jDefinedClass) { JDocComment docComment = jDefinedClass.javadoc(); docComment.addAll(Arrays.asList(serviceIdl.getService().getDescription())); } private void addAnnotations(JCodeModel jCodeModel, JDefinedClass jDefinedClass) { JAnnotationUse annotationUse = jDefinedClass.annotate(jCodeModel.ref("javax.annotation.Generated")); annotationUse.param("value", getClass().getName()); annotationUse.param("date", new Date().toString()); annotationUse.param("comments", "EDIT THIS IF YOU ARE ****"); } private void addNameAnnotations(JDefinedClass jDefinedClass, String name) { JAnnotationUse nameAnnotation = jDefinedClass.annotate(Name.class); nameAnnotation.param("value", name); } private void addDescriptionAnnotations(JDefinedClass jDefinedClass, String shortDescription, String verboseDescription) { JAnnotationUse descriptionAnnotation = jDefinedClass.annotate(Description.class); descriptionAnnotation.param("value", shortDescription); descriptionAnnotation.param("verbose", verboseDescription); } private void addVersionAnnotations(JDefinedClass jDefinedClass, int major, int minor, int patch) { JAnnotationUse versionAnnotation = jDefinedClass.annotate(Version.class); versionAnnotation.param("major", major); versionAnnotation.param("minor", minor); versionAnnotation.param("patch", patch); } private void addExtends(JCodeModel jCodeModel, JDefinedClass jDefinedClass) { jDefinedClass._extends(jCodeModel.ref(ServiceClient.class)); } private void addInterfaceFields(ServiceIDL serviceIdl, JCodeModel jCodeModel, JDefinedClass jDefinedClass) { if (serviceIdl.getService().getObjectMapperClass() != null) { int fieldModifier = JMod.PUBLIC | JMod.FINAL; JClass objectMapper = jCodeModel.ref(serviceIdl.getService().getObjectMapperClass()); jDefinedClass.field(fieldModifier, objectMapper, "customObjectMapper", JExpr._new(objectMapper)); } } private void addExtendsImplements(ServiceIDL serviceIdl, JCodeModel jCodeModel, JDefinedClass jDefinedClass) { jDefinedClass._extends(jCodeModel.ref(AbstractServiceClient.class)); jDefinedClass._implements(jCodeModel.ref(getInterfaceName(serviceIdl))); } private void addConstructor(ServiceIDL serviceIdl, JDefinedClass jDefinedClass) { JFieldRef arg2; JMethod constructor = jDefinedClass.constructor(JMod.PRIVATE); JBlock block = constructor.body(); if (serviceIdl.getExceptions() != null && serviceIdl.getExceptions().size() > 0) { for (Integer serviceResponseCode : serviceIdl.getExceptions().keySet()) { arg2 = JExpr.ref(JExpr.ref(serviceIdl.getExceptions().get(serviceResponseCode) + "Exception"), "class"); block.invoke(JExpr.ref("exceptions"), "put").arg(serviceResponseCode.toString()).arg(arg2); } } // add default exception arg2 = JExpr.ref(JExpr.ref(serviceIdl.getService().getName() + "Exception"), "class"); block.invoke(JExpr.ref("exceptions"), "put").arg("default").arg(arg2); } private void addMethods(ServiceIDL serviceIdl, JCodeModel jCodeModel, JDefinedClass jDefinedClass, boolean isImpl) { for (Map.Entry<String, EndPoint> entry : serviceIdl.getEndPoints().entrySet()) { String methodName = entry.getKey(); EndPoint endPoint = entry.getValue(); JType methodReturnType = getJType(jCodeModel, endPoint.getResponseObject()); JType methodFullReturnType = jCodeModel.ref(FutureTaskResultToDomainObjectPromiseWrapper.class) .narrow(methodReturnType); JMethod method = jDefinedClass.method(JMod.PUBLIC, methodFullReturnType, methodName); if (isImpl) method.annotate(jCodeModel.ref("Override")); JDocComment methodComment = method.javadoc(); methodComment.addAll(Arrays.asList(endPoint.getDescription())); String[] parameters = endPoint.getParameters(); if (parameters == null) parameters = new String[] {}; for (String paramName : parameters) { generateMethodParam(serviceIdl, jCodeModel, method, methodComment, paramName); } Map<String, String> headersMap = getAllHeaders(serviceIdl, endPoint); for (Map.Entry<String, String> headerMapEntry : headersMap.entrySet()) { String value = headerMapEntry.getValue(); Matcher matcher = PARAMETERS_PATTERN.matcher(value); if (matcher.find()) { String paramName = matcher.group(1); generateMethodParam(serviceIdl, jCodeModel, method, methodComment, paramName); } } if (endPoint.getRequestObject() != null) { method.param(getJType(jCodeModel, endPoint.getRequestObject()), REQUEST_OBJECT_VAR_NAME); methodComment.addParam(REQUEST_OBJECT_VAR_NAME); } if (endPoint.getResponseObject() != null && !endPoint.getResponseObject().isEmpty()) { JCommentPart returnComment = methodComment.addReturn(); returnComment.append(methodFullReturnType); } method._throws(jCodeModel.directClass("Exception")); if (isImpl) { addMethodBody(serviceIdl, jCodeModel, endPoint, method.body()); } } } private void addMethodBody(ServiceIDL serviceIdl, JCodeModel jCodeModel, EndPoint endPoint, JBlock block) { if (endPoint.getRequestSplitterClass() != null) { JClass requestSplitter = jCodeModel.ref(endPoint.getRequestSplitterClass()); block.decl(requestSplitter, "requestSplitter", JExpr._new(requestSplitter)); JClass responseMerger = jCodeModel.ref(endPoint.getResponseMergerClass()); block.decl(responseMerger, "responseMerger", JExpr._new(responseMerger)); JClass wrapper = jCodeModel.ref(FutureTaskResultToDomainObjectPromiseWrapper.class); block.decl(wrapper, "wrapper", JExpr._new(wrapper).arg(JExpr.ref("responseMerger"))); if (endPoint.getRequestParamWithLimit() != null) { Matcher matcher = PARAMETERS_PATTERN.matcher(endPoint.getRequestParamWithLimit()); if (matcher.matches()) { Parameter parameter = serviceIdl.getParameters().get(matcher.group(1)); JClass listClass = jCodeModel.ref(parameter.getType()); String listClassVar = matcher.group(1) + "List"; String listElement = listClassVar + "Element"; block.decl(jCodeModel.ref(List.class).narrow(listClass), listClassVar, JExpr.ref("requestSplitter").invoke("split").arg(JExpr.ref(matcher.group(1)))); JForEach forEach = new JForEach(listClass, listElement, JExpr.ref(listClassVar)); JInvocation invocation = createRequest(serviceIdl, jCodeModel, endPoint, forEach.body(), matcher.group(1), listElement); forEach.body().add(JExpr.ref("wrapper").invoke("addFutureForTask") .arg(invocation.invoke("getFutureList"))); block.add(forEach); block._return(JExpr.ref("wrapper")); return; } } else if (endPoint.getRequestSplitterClass() != null) { JInvocation invocation = createRequest(serviceIdl, jCodeModel, endPoint, block, null, null); JType returnType = jCodeModel.ref(List.class) .narrow(getJType(jCodeModel, endPoint.getRequestObject())); block.decl(returnType, "requestObjects", JExpr.ref("requestSplitter").invoke("split").arg(JExpr.ref(REQUEST_OBJECT_VAR_NAME))); JForEach forEach = new JForEach(getJType(jCodeModel, endPoint.getRequestObject()), REQUEST_OBJECT_LOOP_VAR_NAME, JExpr.ref("requestObjects")); forEach.body().add( JExpr.ref("wrapper").invoke("addFutureForTask").arg(invocation.invoke("getFutureList"))); block.add(forEach); block._return(JExpr.ref("wrapper")); return; } } JInvocation invocation = createRequest(serviceIdl, jCodeModel, endPoint, block, null, null); block._return(invocation); } private JInvocation createRequest(ServiceIDL serviceIdl, JCodeModel jCodeModel, EndPoint endPoint, JBlock block, String requestParamWithLimit, String listElementVarName) { String baseUri = serviceIdl.getService().getBaseUri(); String endPointUri = endPoint.getUri(); String uri = (baseUri + endPointUri).replaceAll("//", "/"); Set<String> argsList = new LinkedHashSet<>(); Set<String> argsListQueryParams = new LinkedHashSet<>(); Matcher matcher = PARAMETERS_PATTERN.matcher(uri); while (matcher.find()) { uri = matcher.replaceFirst("%s"); argsList.add(matcher.group(1)); matcher.reset(uri); } if (argsList.isEmpty()) { block.decl(jCodeModel.ref("String"), "uri", JExpr.lit(uri)); } else { JInvocation invocation = JExpr.ref("String").invoke("format").arg(uri); for (String arg : argsList) { Parameter parameter = serviceIdl.getParameters().get(arg); if (endPoint.getRequestParamWithLimit() != null && requestParamWithLimit.equals(arg)) { arg = listElementVarName; } if (parameter.getType().equals("String")) { invocation.arg(JExpr.invoke("encodeUrl").arg(JExpr.ref(arg))); } else if (parameter.getType().endsWith("[]")) { JExpression joinerExpression = jCodeModel.ref(Joiner.class).staticInvoke("on") .arg(JExpr.lit(',')).invoke("join").arg(JExpr.ref(arg)); invocation.arg(JExpr.invoke("encodeUrl").arg(joinerExpression)); } else if (parameter.getType().startsWith("java.util.List")) { invocation.arg( jCodeModel.ref(StringUtils.class).staticInvoke("join").arg(JExpr.ref(arg)).arg(",")); } else { invocation.arg(JExpr.ref(arg)); } } block.decl(jCodeModel.ref("String"), "uri", invocation); } if (endPoint.getParameters() != null) { for (String paramName : endPoint.getParameters()) { Parameter parameter = serviceIdl.getParameters().get(paramName); if (!parameter.getOptional()) { if (parameter.getType().equalsIgnoreCase("string") || parameter.getType().endsWith("[]")) { block.add(jCodeModel.ref(Validate.class).staticInvoke("notEmpty").arg(JExpr.ref(paramName)) .arg(paramName + " can not be null/empty")); } else { block.add(jCodeModel.ref(Validate.class).staticInvoke("notNull").arg(JExpr.ref(paramName)) .arg(paramName + " can not be null")); } } if (argsList.contains(paramName)) continue; argsListQueryParams.add(paramName); } } if (!argsListQueryParams.isEmpty()) { JInvocation invocation = jCodeModel.ref(Arrays.class).staticInvoke("asList"); for (String arg : argsListQueryParams) { Parameter parameter = serviceIdl.getParameters().get(arg); String argRef = arg; if (endPoint.getRequestParamWithLimit() != null && requestParamWithLimit.equals(arg)) { argRef = listElementVarName; } String paramName = Optional.ofNullable(parameter.getName()).orElse(arg); if (!parameter.getOptional()) { if (parameter.isMultiValue()) { invocation.arg(JExpr.invoke("getMultiValueParamURI").arg(paramName).arg(JExpr.ref(argRef))); } else if (parameter.getType().equals("String")) { invocation.arg( JExpr.lit(paramName + "=").plus(JExpr.invoke("encodeUrl").arg(JExpr.ref(argRef)))); } else if (parameter.getType().endsWith("[]")) { JExpression joinerExpression = jCodeModel.ref(Joiner.class).staticInvoke("on") .arg(JExpr.lit(',')).invoke("join").arg(JExpr.ref(argRef)); invocation.arg( JExpr.lit(paramName + "=").plus(JExpr.invoke("encodeUrl").arg(joinerExpression))); } else if (parameter.getType().startsWith("java.util.List")) { JExpression joinerExpression = jCodeModel.ref(StringUtils.class).staticInvoke("join") .arg(JExpr.ref(argRef)).arg(","); invocation.arg( JExpr.lit(paramName + "=").plus(JExpr.invoke("encodeUrl").arg(joinerExpression))); } else { invocation.arg(JExpr.lit(paramName + "=").plus(JExpr.ref(argRef))); } } else { if (parameter.isMultiValue()) { invocation.arg(JExpr.invoke("getMultiValueParamURI").arg(paramName).arg(JExpr.ref(argRef))); } else { invocation.arg(JExpr.invoke("getOptURI").arg(paramName).arg(JExpr.ref(argRef))); } } } block.assign(JExpr.ref("uri"), JExpr.ref("uri").plus(JExpr.invoke("getQueryURI").arg(invocation))); } Map<String, String> headersMap = getAllHeaders(serviceIdl, endPoint); if (headersMap.size() > 0) { JClass mapClass = jCodeModel.ref(Map.class).narrow(jCodeModel.ref("String"), jCodeModel.ref("String")); JClass hashMapClass = jCodeModel.ref(HashMap.class).narrow(jCodeModel.ref("String"), jCodeModel.ref("String")); block.decl(mapClass, "headersMap", JExpr._new(hashMapClass)); for (Map.Entry<String, String> headerMapEntry : headersMap.entrySet()) { String key = headerMapEntry.getKey(); String value = headerMapEntry.getValue(); JInvocation invocation = JExpr.invoke(JExpr.ref("headersMap"), "put").arg(key); matcher = PARAMETERS_PATTERN.matcher(value); if (matcher.find()) { String paramName = matcher.group(1); invocation.arg(jCodeModel.ref("String").staticInvoke("valueOf").arg(JExpr.ref(paramName))); Parameter parameter = serviceIdl.getParameters().get(paramName); if (parameter.getOptional()) { block._if(JExpr.ref(paramName).ne(JExpr._null()))._then().add(invocation); } else { block.add(invocation); } } else { invocation.arg(value); block.add(invocation); } } } if (endPoint.getResponseObject() != null && !endPoint.getResponseObject().isEmpty()) { // If responseObject contains generic types, use TypeReference. Else use Class of the responseObject. // http://wiki.fasterxml.com/JacksonDataBinding // For uniformity, TypeReference or Class is then converted to a JavaType to deserialize service response. // For generic types, creating an anonymous inner class for every service call would have overhead which is // compensated by the type safety it ensures at compilation time as well as easy code generation JInvocation invocation = JExpr.invoke("execute"); JType definedClass = jCodeModel._ref(ServiceExecutePropertiesBuilder.class); JInvocation nestedInvocation = JExpr.invoke("getJavaType"); if (!endPoint.getResponseObject().contains("<")) { if (endPoint.getResponseObject().contains(".")) { JInvocation classDecl = jCodeModel.ref(Class.class).staticInvoke("forName") .arg(endPoint.getResponseObject()); nestedInvocation.arg(classDecl); } else { JFieldRef ref = JExpr.ref(JExpr.ref(endPoint.getResponseObject()), "class"); nestedInvocation.arg(ref); } } else { JClass typeReferenceClass = jCodeModel.ref(TypeReference.class) .narrow(getJType(jCodeModel, endPoint.getResponseObject())); nestedInvocation.arg(JExpr._new(jCodeModel.anonymousClass(typeReferenceClass))); } JInvocation builderInvocation = JExpr.invoke(JExpr._new(definedClass), "setJavaType") .arg(nestedInvocation); if (endPoint.getErrorResponseObject() != null && !endPoint.getErrorResponseObject().isEmpty()) { JInvocation nestedErrorInvocation = JExpr.invoke("getErrorType"); if (!endPoint.getErrorResponseObject().contains("<")) { if (endPoint.getErrorResponseObject().contains(".")) { JInvocation classDecl = jCodeModel.ref(Class.class).staticInvoke("forName") .arg(endPoint.getErrorResponseObject()); nestedErrorInvocation.arg(classDecl); } else { JFieldRef ref = JExpr.ref(JExpr.ref(endPoint.getErrorResponseObject()), "class"); nestedErrorInvocation.arg(ref); } } else { JClass typeReferenceClass = jCodeModel.ref(TypeReference.class) .narrow(getJType(jCodeModel, endPoint.getErrorResponseObject())); nestedErrorInvocation.arg(JExpr._new(jCodeModel.anonymousClass(typeReferenceClass))); } builderInvocation = builderInvocation.invoke("setErrorType").arg(nestedErrorInvocation); } builderInvocation = builderInvocation.invoke("setUri").arg(JExpr.ref("uri")); builderInvocation = builderInvocation.invoke("setHttpMethod").arg(endPoint.getHttpMethod()); if (headersMap.size() > 0) { builderInvocation = builderInvocation.invoke("setHeadersMap").arg(JExpr.ref("headersMap")); } if (endPoint.getRequestObject() != null && !endPoint.getRequestObject().isEmpty()) { String requestObjectName; if (endPoint.getRequestSplitterClass() != null && endPoint.getRequestParamWithLimit() == null) { requestObjectName = REQUEST_OBJECT_LOOP_VAR_NAME; } else { requestObjectName = REQUEST_OBJECT_VAR_NAME; } builderInvocation = builderInvocation.invoke("setRequestObject").arg(JExpr.ref(requestObjectName)); } if (endPoint.getCommandName() != null && !endPoint.getCommandName().isEmpty()) { builderInvocation = builderInvocation.invoke("setCommandName").arg(endPoint.getCommandName()); } if (endPoint.isRequestCachingEnabled()) { builderInvocation = builderInvocation.invoke("setRequestCachingEnabled").arg(JExpr.lit(true)); } builderInvocation = builderInvocation.invoke("build"); return invocation.arg(builderInvocation); } return null; } private void addSimpleMethod(JCodeModel jCodeModel, JDefinedClass jDefinedClass, String methodName, String returnStr) { addSimpleMethod(jCodeModel, jDefinedClass, methodName, returnStr, null); } private void addSimpleMethod(JCodeModel jCodeModel, JDefinedClass jDefinedClass, String methodName, String returnStr, String exception) { JType methodReturnType = jCodeModel.ref("String"); JMethod method = jDefinedClass.method(JMod.PUBLIC, methodReturnType, methodName); method.annotate(jCodeModel.ref("Override")); method.javadoc().addReturn().append(methodReturnType); if (exception != null) method._throws(jCodeModel.directClass(exception)); method.body()._return(JExpr.lit(returnStr)); } private void addImplMethods(ServiceIDL serviceIdl, JCodeModel jCodeModel, JDefinedClass jDefinedClass) { String commandName = serviceIdl.getService().getCommandName(); if (commandName == null || commandName.isEmpty()) { commandName = Introspector.decapitalize(serviceIdl.getService().getName() + "HttpRequest"); } addSimpleMethod(jCodeModel, jDefinedClass, "getCommandName", commandName); if (serviceIdl.getService().getObjectMapperClass() != null) { JType methodReturnType = jCodeModel.ref(ObjectMapper.class); JMethod method = jDefinedClass.method(JMod.PROTECTED, methodReturnType, "getObjectMapper"); method.annotate(jCodeModel.ref("Override")); method.javadoc().addReturn().append(methodReturnType); method.body()._return(JExpr.ref("customObjectMapper").invoke("getObjectMapper")); } } public void generateInterface(ServiceIDL serviceIdl, JCodeModel jCodeModel, String destinationFolder) throws Exception { JDefinedClass serviceInterface = jCodeModel._class(getFullInterfaceName(serviceIdl), ClassType.INTERFACE); addClassComments(serviceIdl, serviceInterface); addAnnotations(jCodeModel, serviceInterface); addExtends(jCodeModel, serviceInterface); addInterfaceFields(serviceIdl, jCodeModel, serviceInterface); addMethods(serviceIdl, jCodeModel, serviceInterface, false); jCodeModel.build(new File(destinationFolder), (PrintStream) null); } public void generateImpl(ServiceIDL serviceIdl, JCodeModel jCodeModel, String destinationFolder) throws Exception { JDefinedClass serviceImpl = jCodeModel._class(getFullImplName(serviceIdl), ClassType.CLASS); addClassComments(serviceIdl, serviceImpl); addAnnotations(jCodeModel, serviceImpl); addExtendsImplements(serviceIdl, jCodeModel, serviceImpl); addConstructor(serviceIdl, serviceImpl); addMethods(serviceIdl, jCodeModel, serviceImpl, true); addImplMethods(serviceIdl, jCodeModel, serviceImpl); addMetaAnnotations(serviceIdl, jCodeModel, serviceImpl); jCodeModel.build(new File(destinationFolder), (PrintStream) null); } private void addMetaAnnotations(ServiceIDL serviceIdl, JCodeModel jCodeModel, JDefinedClass serviceImpl) { String serviceName = canonicalName(getInterfaceName(serviceIdl), "ServiceClient", "SC"); addNameAnnotations(serviceImpl, serviceName); addVersionAnnotations(serviceImpl, serviceIdl.getVersion().getMajor(), serviceIdl.getVersion().getMinor(), serviceIdl.getVersion().getPatch()); String[] description = serviceIdl.getService().getDescription(); String shortDescription = description.length > 0 ? description[0] : getInterfaceName(serviceIdl); String fullDescription = description.length > 0 ? String.join(" ", description) : getInterfaceName(serviceIdl); addDescriptionAnnotations(serviceImpl, shortDescription, fullDescription); } }