From 98b410c174a21f259cef5ae90ecb857c1a5f8667 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 12 Jun 2024 08:15:37 +0530 Subject: [PATCH] Add runtime support --- .../stdlib/http/api/HTTPServicesRegistry.java | 34 +++++++++- .../stdlib/http/api/HttpService.java | 28 ++++---- .../http/api/HttpServiceFromContract.java | 65 +++++++++++++++++++ 3 files changed, 114 insertions(+), 13 deletions(-) create mode 100644 native/src/main/java/io/ballerina/stdlib/http/api/HttpServiceFromContract.java diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HTTPServicesRegistry.java b/native/src/main/java/io/ballerina/stdlib/http/api/HTTPServicesRegistry.java index 9bca326543..cc6c2606b5 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HTTPServicesRegistry.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HTTPServicesRegistry.java @@ -21,19 +21,27 @@ import io.ballerina.runtime.api.Runtime; import io.ballerina.runtime.api.creators.ErrorCreator; +import io.ballerina.runtime.api.types.ReferenceType; +import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; +import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.api.values.BTypedesc; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import static io.ballerina.stdlib.http.api.HttpConstants.DEFAULT_HOST; +import static io.ballerina.stdlib.http.api.HttpUtil.checkConfigAnnotationAvailability; /** * This services registry holds all the services of HTTP + WebSocket. This is a singleton class where all HTTP + @@ -44,6 +52,7 @@ public class HTTPServicesRegistry { private static final Logger logger = LoggerFactory.getLogger(HTTPServicesRegistry.class); + private static final BString SERVICE_TYPE = StringUtils.fromString("serviceType"); protected Map servicesMapByHost = new ConcurrentHashMap<>(); protected Map servicesByBasePath; @@ -98,7 +107,10 @@ public List getSortedServiceURIsByHost(String hostName) { * @param basePath absolute resource path of the service */ public void registerService(BObject service, String basePath) { - HttpService httpService = HttpService.buildHttpService(service, basePath); + Optional serviceContractType = getServiceContractType(service); + HttpService httpService = serviceContractType.map(referenceType -> + HttpServiceFromContract.buildHttpService(service, basePath, referenceType)).orElseGet( + () -> HttpService.buildHttpService(service, basePath)); service.addNativeData(HttpConstants.ABSOLUTE_RESOURCE_PATH, basePath); String hostName = httpService.getHostName(); if (servicesMapByHost.get(hostName) == null) { @@ -127,6 +139,26 @@ public void registerService(BObject service, String basePath) { sortedServiceURIs.sort((basePath1, basePath2) -> basePath2.length() - basePath1.length()); } + + private static Optional getServiceContractType(BObject service) { + BMap serviceConfig = HttpService.getHttpServiceConfigAnnotation(service); + if (!checkConfigAnnotationAvailability(serviceConfig)) { + return Optional.empty(); + } + + Object serviceType = ((BMap) serviceConfig).get(SERVICE_TYPE); + if (Objects.isNull(serviceType) || !(serviceType instanceof BTypedesc serviceTypeDesc)) { + return Optional.empty(); + } + + Type serviceContractType = serviceTypeDesc.getDescribingType(); + if (Objects.isNull(serviceContractType) || + !(serviceContractType instanceof ReferenceType serviceContractRefType)) { + return Optional.empty(); + } + return Optional.of(serviceContractRefType); + } + public String findTheMostSpecificBasePath(String requestURIPath, Map services, List sortedServiceURIs) { for (Object key : sortedServiceURIs) { diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java index 86b0318411..bb8553990b 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java @@ -26,6 +26,7 @@ import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.MethodType; import io.ballerina.runtime.api.types.ObjectType; +import io.ballerina.runtime.api.types.ResourceMethodType; import io.ballerina.runtime.api.types.ServiceType; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; @@ -71,13 +72,13 @@ public class HttpService implements Service { private static final Logger log = LoggerFactory.getLogger(HttpService.class); protected static final BString BASE_PATH_FIELD = fromString("basePath"); - private static final BString CORS_FIELD = fromString("cors"); + protected static final BString CORS_FIELD = fromString("cors"); private static final BString VERSIONING_FIELD = fromString("versioning"); - private static final BString HOST_FIELD = fromString("host"); - private static final BString OPENAPI_DEF_FIELD = fromString("openApiDefinition"); - private static final BString MEDIA_TYPE_SUBTYPE_PREFIX = fromString("mediaTypeSubtypePrefix"); - private static final BString TREAT_NILABLE_AS_OPTIONAL = fromString("treatNilableAsOptional"); - private static final BString DATA_VALIDATION = fromString("validation"); + protected static final BString HOST_FIELD = fromString("host"); + protected static final BString OPENAPI_DEF_FIELD = fromString("openApiDefinition"); + protected static final BString MEDIA_TYPE_SUBTYPE_PREFIX = fromString("mediaTypeSubtypePrefix"); + protected static final BString TREAT_NILABLE_AS_OPTIONAL = fromString("treatNilableAsOptional"); + protected static final BString DATA_VALIDATION = fromString("validation"); private BObject balService; private List resources; @@ -115,7 +116,7 @@ public void setKeepAlive(boolean keepAlive) { this.keepAlive = keepAlive; } - private void setCompressionConfig(BMap compression) { + protected void setCompressionConfig(BMap compression) { this.compression = compression; } @@ -259,10 +260,9 @@ public static HttpService buildHttpService(BObject service, String basePath) { return httpService; } - private static void processResources(HttpService httpService) { + protected static void processResources(HttpService httpService) { List httpResources = new ArrayList<>(); - for (MethodType resource : ((ServiceType) TypeUtils.getType( - httpService.getBalService())).getResourceMethods()) { + for (MethodType resource : httpService.getResourceMethods()) { if (!SymbolFlags.isFlagOn(resource.getFlags(), SymbolFlags.RESOURCE)) { continue; } @@ -280,6 +280,10 @@ private static void processResources(HttpService httpService) { httpService.setResources(httpResources); } + protected ResourceMethodType[] getResourceMethods() { + return ((ServiceType) TypeUtils.getType(balService)).getResourceMethods(); + } + private static void processLinks(HttpService httpService, List httpResources) { for (HttpResource targetResource : httpResources) { for (HttpResource.LinkedResourceInfo link : targetResource.getLinkedResources()) { @@ -393,7 +397,7 @@ private static void updateResourceTree(HttpService httpService, List) serviceConfig.get(HttpConstants.ANN_CONFIG_ATTR_COMPRESSION)); + httpService.setChunkingConfig(serviceConfig.get(HttpConstants.ANN_CONFIG_ATTR_CHUNKING).toString()); + httpService.setCorsHeaders(CorsHeaders.buildCorsHeaders(serviceConfig.getMapValue(CORS_FIELD))); + httpService.setHostName(serviceConfig.getStringValue(HOST_FIELD).getValue().trim()); + httpService.setIntrospectionPayload(serviceConfig.getArrayValue(OPENAPI_DEF_FIELD).getByteArray()); + if (serviceConfig.containsKey(MEDIA_TYPE_SUBTYPE_PREFIX)) { + httpService.setMediaTypeSubtypePrefix(serviceConfig.getStringValue(MEDIA_TYPE_SUBTYPE_PREFIX) + .getValue().trim()); + } + httpService.setTreatNilableAsOptional(serviceConfig.getBooleanValue(TREAT_NILABLE_AS_OPTIONAL)); + httpService.setConstraintValidation(serviceConfig.getBooleanValue(DATA_VALIDATION)); + } else { + httpService.setHostName(HttpConstants.DEFAULT_HOST); + } + processResources(httpService); + httpService.setAllAllowedMethods(DispatcherUtil.getAllResourceMethods(httpService)); + return httpService; + } + + public static BMap getHttpServiceConfigAnnotation(ReferenceType serviceContractType) { + String packagePath = ModuleUtils.getHttpPackageIdentifier(); + String annotationName = HttpConstants.ANN_NAME_HTTP_SERVICE_CONFIG; + String key = packagePath.replaceAll(HttpConstants.REGEX, HttpConstants.SINGLE_SLASH); + if (!(serviceContractType instanceof AnnotatableType annotatableServiceContractType)) { + return null; + } + return (BMap) annotatableServiceContractType.getAnnotation(fromString(key + ":" + annotationName)); + } + + @Override + protected ResourceMethodType[] getResourceMethods() { + return ((ServiceType) TypeUtils.getReferredType(serviceContractType)).getResourceMethods(); + } +}