Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com)
*
* WSO2 LLC. licenses this file to you 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.ballerina.modelgenerator.commons;

/**
* Represents read-only metadata for a service.
*
* @param metadataKey The key/name of the metadata field
* @param displayName The display name for the metadata field
* @param kind The kind/category of the metadata field (e.g., ANNOTATION, SERVICE_DESCRIPTION, LISTENER_PARAM)
*
* @since 1.0.0
Copy link
Contributor

@pasindufernando1 pasindufernando1 Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @since 1.0.0
* @since 1.3.0

*/
public record ReadOnlyMetaData(
String metadataKey,
String displayName,
String kind
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,36 @@ public List<Annotation> getAnnotationAttachments(String org, String packageName,

}

public List<ReadOnlyMetaData> getReadOnlyMetaData(String orgName, String packageName, String serviceType) {
String sql = "SELECT " +
"metadata_key, " +
"display_name, " +
"kind " +
"FROM ServiceReadOnlyMetaData srmd " +
"JOIN Package p ON srmd.package_id = p.package_id " +
"WHERE p.name = ? AND p.org = ? ";
try (Connection conn = DriverManager.getConnection(dbPath);
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, packageName);
stmt.setString(2, orgName);

ResultSet rs = stmt.executeQuery();
List<ReadOnlyMetaData> metaDataList = new ArrayList<>();
while (rs.next()) {
metaDataList.add(new ReadOnlyMetaData(
rs.getString("metadata_key"),
rs.getString("display_name"),
rs.getString("kind")
));
}
conn.close();
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The connection is already being closed by the try-with-resources statement. This explicit conn.close() call is redundant and should be removed.

Copilot uses AI. Check for mistakes.
return metaDataList;
} catch (SQLException e) {
Logger.getGlobal().severe("Error executing query: " + e.getMessage());
return List.of();
}
}

// Helper builder class
private static class ParameterDataBuilder {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.ballerina.projects.Document;
import io.ballerina.projects.Project;
import io.ballerina.servicemodelgenerator.extension.builder.service.AiChatServiceBuilder;
import io.ballerina.servicemodelgenerator.extension.builder.service.AsbServiceBuilder;
import io.ballerina.servicemodelgenerator.extension.builder.service.DefaultServiceBuilder;
import io.ballerina.servicemodelgenerator.extension.builder.service.GraphqlServiceBuilder;
import io.ballerina.servicemodelgenerator.extension.builder.service.HttpServiceBuilder;
Expand Down Expand Up @@ -54,6 +55,7 @@
import java.util.function.Supplier;

import static io.ballerina.servicemodelgenerator.extension.util.Constants.AI;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.ASB;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.GRAPHQL;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.HTTP;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.KAFKA;
Expand All @@ -77,6 +79,7 @@ public class ServiceBuilderRouter {
put(GRAPHQL, GraphqlServiceBuilder::new);
put(MCP, McpServiceBuilder::new);
put(KAFKA, KafkaServiceBuilder::new);
put(ASB, AsbServiceBuilder::new);
}};

public static ServiceNodeBuilder getServiceBuilder(String protocol) {
Expand Down Expand Up @@ -108,7 +111,9 @@ public static Service getServiceFromSource(Node node, Project project,
workspaceManager, filePath, serviceMetadata.serviceType(), moduleID.orgName(),
moduleID.packageName(), moduleID.moduleName(), moduleID.version());
Service service = serviceBuilder.getModelFromSource(context);
service.getProperties().forEach((k, v) -> v.setAdvanced(false));
if (service != null) {
service.getProperties().forEach((k, v) -> v.setAdvanced(false));
}
return service;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
import io.ballerina.servicemodelgenerator.extension.model.context.ModelFromSourceContext;
import io.ballerina.servicemodelgenerator.extension.model.context.UpdateModelContext;
import io.ballerina.servicemodelgenerator.extension.util.ListenerUtil;
import io.ballerina.servicemodelgenerator.extension.util.ServiceModelUtils;
import io.ballerina.servicemodelgenerator.extension.extractor.ReadOnlyMetadataManager;
import io.ballerina.servicemodelgenerator.extension.extractor.CustomExtractor;
import io.ballerina.servicemodelgenerator.extension.util.Utils;
import io.ballerina.tools.text.LinePosition;
import io.ballerina.tools.text.LineRange;
Expand All @@ -71,6 +74,7 @@
import static io.ballerina.servicemodelgenerator.extension.util.Constants.ARG_TYPE_LISTENER_PARAM_REQUIRED;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.ARG_TYPE_LISTENER_VAR_NAME;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.CLOSE_BRACE;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.DOUBLE_QUOTE;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.LISTENER_VAR_NAME;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.NEW_LINE;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.NEW_LINE_WITH_TAB;
Expand All @@ -80,6 +84,7 @@
import static io.ballerina.servicemodelgenerator.extension.util.Constants.PROP_KEY_LISTENER;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.PROP_KEY_SERVICE_TYPE;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.PROP_KEY_STRING_LITERAL;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.PROP_READONLY_METADATA_KEY;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.SERVICE;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.SPACE;
import static io.ballerina.servicemodelgenerator.extension.util.Constants.TAB;
Expand All @@ -99,6 +104,7 @@
import static io.ballerina.servicemodelgenerator.extension.util.ServiceModelUtils.getServiceTypeIdentifier;
import static io.ballerina.servicemodelgenerator.extension.util.ServiceModelUtils.getStringLiteral;
import static io.ballerina.servicemodelgenerator.extension.util.ServiceModelUtils.getTypeDescriptorProperty;
import static io.ballerina.servicemodelgenerator.extension.util.ServiceModelUtils.getReadonlyMetadata;
import static io.ballerina.servicemodelgenerator.extension.util.ServiceModelUtils.populateRequiredFunctionsForServiceType;
import static io.ballerina.servicemodelgenerator.extension.util.ServiceModelUtils.updateListenerItems;
import static io.ballerina.servicemodelgenerator.extension.util.ServiceModelUtils.updateServiceInfoNew;
Expand Down Expand Up @@ -288,7 +294,10 @@ public Optional<Service> getModelTemplate(GetModelContext context) {
if (serviceTemplate.optionalStringLiteral() == 0) {
properties.put(PROP_KEY_STRING_LITERAL, getStringLiteral(serviceTemplate));
}

// Get the service type for metadata retrieval
List<String> serviceTypes = ServiceDatabaseManager.getInstance().getServiceTypes(pkg.packageId());
String serviceType = serviceTypes.isEmpty() ? null : serviceTypes.getFirst();
properties.put(PROP_READONLY_METADATA_KEY, getReadonlyMetadata(pkg.org(), pkg.name(), serviceType));
List<AnnotationAttachment> annotationAttachments = ServiceDatabaseManager.getInstance()
.getAnnotationAttachments(pkg.packageId());
for (AnnotationAttachment annotationAttachment : annotationAttachments) {
Expand Down Expand Up @@ -470,6 +479,7 @@ private void populateServiceModelFromSource(Service serviceModel, ServiceDeclara
updateServiceDocs(serviceNode, serviceModel);
updateAnnotationAttachmentProperty(serviceNode, serviceModel);
updateListenerItems(context.moduleName(), context.semanticModel(), context.project(), serviceModel);
updateReadOnlyMetadataWithAnnotations(serviceModel, serviceNode, context);
}

/**
Expand Down Expand Up @@ -568,6 +578,82 @@ static void buildServiceNodeBody(List<String> functions, StringBuilder builder)
builder.append(String.join(TWO_NEW_LINES, functions)).append(NEW_LINE).append(CLOSE_BRACE);
}

public static void extractServicePathInfo(ServiceDeclarationNode serviceNode, Service serviceModel) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use the method in the this util.

String attachPoint = getPath(serviceNode.absoluteResourcePath());
if (!attachPoint.isEmpty()) {
boolean isStringLiteral = attachPoint.startsWith(DOUBLE_QUOTE) && attachPoint.endsWith(DOUBLE_QUOTE);
if (isStringLiteral) {
Value stringLiteralProperty = serviceModel.getStringLiteralProperty();
if (Objects.nonNull(stringLiteralProperty)) {
stringLiteralProperty.setValue(attachPoint);
} else {
serviceModel.setStringLiteral(ServiceModelUtils.getStringLiteralProperty(attachPoint));
}
} else {
Value basePathProperty = serviceModel.getBasePath();
if (Objects.nonNull(basePathProperty)) {
basePathProperty.setValue(attachPoint);
} else {
serviceModel.setBasePath(ServiceModelUtils.getBasePathProperty(attachPoint));
}
}
}
}

/**
* Updates the readOnly metadata in the service model using the new extraction architecture.
* This method uses the ReadOnlyMetadataManager to extract values from different sources
* based on the metadata kind (ANNOTATION, LISTENER_PARAM, SERVICE_DESCRIPTION, CUSTOM).
*
* @param serviceModel The service model to update
* @param serviceNode The service declaration node containing annotations
* @param context The model context containing service information
*/
protected void updateReadOnlyMetadataWithAnnotations(Service serviceModel, ServiceDeclarationNode serviceNode,
ModelFromSourceContext context) {
// Get the current readOnly metadata property
Value currentReadOnlyMetadata = serviceModel.getProperty("readOnlyMetaData");
if (currentReadOnlyMetadata == null) {
// readOnlyMetaData property should be initialized by service builders before calling this method
return;
}

// Create the ReadOnlyMetadataManager
ReadOnlyMetadataManager metadataManager = new ReadOnlyMetadataManager();

// Check if the current service builder supports custom extraction
Optional<CustomExtractor> customExtractor = Optional.empty();
if (this instanceof CustomExtractor customServiceBuilder) {
customExtractor = Optional.of(customServiceBuilder);
}

// Extract all metadata values using the new architecture
Map<String, List<String>> extractedValues = metadataManager.extractAllMetadata(serviceNode, context, customExtractor);

// Get or initialize the metadata map
HashMap<String, List<String>> currentProps;
try {
currentProps = (HashMap<String, List<String>>) currentReadOnlyMetadata.getValueAsObject();
if (currentProps == null) {
currentProps = new HashMap<>();
currentReadOnlyMetadata.setValue(currentProps);
}
} catch (ClassCastException e) {
// Value is not the expected type, reinitialize
currentProps = new HashMap<>();
currentReadOnlyMetadata.setValue(currentProps);
}

// Process extracted values - put all values directly into the readOnlyMetaData property
for (Map.Entry<String, List<String>> entry : extractedValues.entrySet()) {
String displayName = entry.getKey();
List<String> values = entry.getValue();

// Convert List<String> to ArrayList<String> and put directly in readOnlyMetaData
currentProps.put(displayName, new ArrayList<>(values));
}
}

public abstract String kind();

protected static Value listenerNameProperty(GetServiceInitModelContext context) {
Expand Down
Loading
Loading