Skip to content

Commit

Permalink
Add runtime support
Browse files Browse the repository at this point in the history
  • Loading branch information
TharmiganK committed Jun 12, 2024
1 parent aae8a69 commit 98b410c
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 +
Expand All @@ -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<String, ServicesMapHolder> servicesMapByHost = new ConcurrentHashMap<>();
protected Map<String, HttpService> servicesByBasePath;
Expand Down Expand Up @@ -98,7 +107,10 @@ public List<String> 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<ReferenceType> 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) {
Expand Down Expand Up @@ -127,6 +139,26 @@ public void registerService(BObject service, String basePath) {
sortedServiceURIs.sort((basePath1, basePath2) -> basePath2.length() - basePath1.length());
}


private static Optional<ReferenceType> getServiceContractType(BObject service) {
BMap serviceConfig = HttpService.getHttpServiceConfigAnnotation(service);
if (!checkConfigAnnotationAvailability(serviceConfig)) {
return Optional.empty();
}

Object serviceType = ((BMap<BString, Object>) 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<String, HttpService> services,
List<String> sortedServiceURIs) {
for (Object key : sortedServiceURIs) {
Expand Down
28 changes: 16 additions & 12 deletions native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<HttpResource> resources;
Expand Down Expand Up @@ -115,7 +116,7 @@ public void setKeepAlive(boolean keepAlive) {
this.keepAlive = keepAlive;
}

private void setCompressionConfig(BMap<BString, Object> compression) {
protected void setCompressionConfig(BMap<BString, Object> compression) {
this.compression = compression;
}

Expand Down Expand Up @@ -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<HttpResource> 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;
}
Expand All @@ -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<HttpResource> httpResources) {
for (HttpResource targetResource : httpResources) {
for (HttpResource.LinkedResourceInfo link : targetResource.getLinkedResources()) {
Expand Down Expand Up @@ -393,7 +397,7 @@ private static void updateResourceTree(HttpService httpService, List<HttpResourc
httpResources.add(httpResource);
}

private static BMap getHttpServiceConfigAnnotation(BObject service) {
public static BMap getHttpServiceConfigAnnotation(BObject service) {
return getServiceConfigAnnotation(service, ModuleUtils.getHttpPackageIdentifier(),
HttpConstants.ANN_NAME_HTTP_SERVICE_CONFIG);
}
Expand Down Expand Up @@ -539,7 +543,7 @@ public boolean getConstraintValidation() {
return constraintValidation;
}

private void setConstraintValidation(boolean constraintValidation) {
protected void setConstraintValidation(boolean constraintValidation) {
this.constraintValidation = constraintValidation;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.ballerina.stdlib.http.api;

import io.ballerina.runtime.api.types.AnnotatableType;
import io.ballerina.runtime.api.types.ReferenceType;
import io.ballerina.runtime.api.types.ResourceMethodType;
import io.ballerina.runtime.api.types.ServiceType;
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.stdlib.http.api.nativeimpl.ModuleUtils;
import io.ballerina.stdlib.http.uri.DispatcherUtil;

import static io.ballerina.runtime.api.utils.StringUtils.fromString;
import static io.ballerina.stdlib.http.api.HttpUtil.checkConfigAnnotationAvailability;

public class HttpServiceFromContract extends HttpService {

private ReferenceType serviceContractType;

protected HttpServiceFromContract(BObject service, String basePath, ReferenceType httpServiceContractType) {
super(service, basePath);
this.serviceContractType = httpServiceContractType;
}

public static HttpService buildHttpService(BObject service, String basePath,
ReferenceType serviceContractType) {
HttpService httpService = new HttpServiceFromContract(service, basePath, serviceContractType);
BMap serviceConfig = getHttpServiceConfigAnnotation(serviceContractType);
if (checkConfigAnnotationAvailability(serviceConfig)) {
httpService.setCompressionConfig(
(BMap<BString, Object>) 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();
}
}

0 comments on commit 98b410c

Please sign in to comment.