Skip to content

4.x: Use Helidon metadata format (HSON) for service registry generated file. #9061

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions all/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,10 @@
<groupId>io.helidon.inject.configdriven</groupId>
<artifactId>helidon-inject-configdriven-processor</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.service</groupId>
<artifactId>helidon-service-metadata</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.service</groupId>
<artifactId>helidon-service-registry</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1436,6 +1436,11 @@
</dependency>

<!-- Service registry -->
<dependency>
<groupId>io.helidon.service</groupId>
<artifactId>helidon-service-metadata</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.service</groupId>
<artifactId>helidon-service-registry</artifactId>
Expand Down
16 changes: 16 additions & 0 deletions codegen/apt/src/main/java/io/helidon/codegen/apt/AptFiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package io.helidon.codegen.apt;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
Expand All @@ -35,6 +36,7 @@
import io.helidon.codegen.CodegenException;
import io.helidon.codegen.CodegenFiler;
import io.helidon.codegen.CodegenOptions;
import io.helidon.codegen.FilerResource;
import io.helidon.codegen.FilerTextResource;
import io.helidon.codegen.IndentType;
import io.helidon.codegen.classmodel.ClassModel;
Expand Down Expand Up @@ -107,6 +109,20 @@ public FilerTextResource textResource(String location, Object... originatingElem
}
}

@Override
public FilerResource resource(String location, Object... originatingElements) {
try {
var resource = filer.getResource(StandardLocation.CLASS_OUTPUT, "", location);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (var is = resource.openInputStream()) {
is.transferTo(baos);
}
return new FilerResourceImpl(filer, location, toElements(originatingElements), resource, baos.toByteArray());
} catch (IOException e) {
return new FilerResourceImpl(filer, location, toElements(originatingElements));
}
}

private Object originatingElement(Element[] elements, Object alternative) {
if (elements.length == 0) {
return alternative;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates.
*
* 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 io.helidon.codegen.apt;

import java.util.Arrays;

import javax.annotation.processing.Filer;
import javax.lang.model.element.Element;
import javax.tools.FileObject;
import javax.tools.StandardLocation;

import io.helidon.codegen.CodegenException;
import io.helidon.codegen.FilerResource;

class FilerResourceImpl implements FilerResource {
private final Filer filer;
private final String location;
private final Element[] originatingElements;
private final FileObject originalResource; // may be null

private byte[] currentBytes;

private boolean modified;

FilerResourceImpl(Filer filer, String location, Element[] originatingElements) {
this.filer = filer;
this.location = location;
this.originatingElements = originatingElements;
this.originalResource = null;
this.currentBytes = new byte[0];
}

FilerResourceImpl(Filer filer,
String location,
Element[] originatingElements,
FileObject originalResource,
byte[] existingBytes) {
this.filer = filer;
this.location = location;
this.originatingElements = originatingElements;
this.originalResource = originalResource;
this.currentBytes = existingBytes;
}

@Override
public byte[] bytes() {
return Arrays.copyOf(currentBytes, currentBytes.length);
}

@Override
public void bytes(byte[] newBytes) {
currentBytes = Arrays.copyOf(newBytes, newBytes.length);
modified = true;
}

@Override
public void write() {
if (modified) {
if (originalResource != null) {
originalResource.delete();
}
try {
FileObject newResource = filer.createResource(StandardLocation.CLASS_OUTPUT,
"",
location,
originatingElements);
try (var os = newResource.openOutputStream()) {
os.write(currentBytes);
}
} catch (Exception e) {
throw new CodegenException("Failed to create resource: " + location, e);
}
}
}
}
12 changes: 12 additions & 0 deletions codegen/codegen/src/main/java/io/helidon/codegen/CodegenFiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ default FilerTextResource textResource(String location, Object... originatingEle
throw new UnsupportedOperationException("Method textResource not implemented yet on " + getClass().getName());
}

/**
* A text resource that can be updated in the output.
* Note that the resource can only be written once per processing round.
*
* @param location location to read/write to in the classes output directory
* @param originatingElements elements that caused this file to be generated
* @return the resource that can be used to update the file
*/
default FilerResource resource(String location, Object... originatingElements) {
throw new UnsupportedOperationException("Method resource not implemented yet on " + getClass().getName());
}

/**
* Write a {@code META-INF/services} file for a specific provider interface and implementation(s).
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates.
*
* 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 io.helidon.codegen;

/**
* A resource from output (such as {@code target/META-INF/helidon}) that can have existing
* values, and may be replaced with a new value.
*/
public interface FilerResource {
/**
* Existing bytes of the resource. Returns empty array, if the resource does not exist or is empty.
*
* @return bytes of the resource
*/
byte[] bytes();

/**
* New bytes of the resource.
*
* @param newBytes new bytes to {@link #write()} to the resource file
*/
void bytes(byte[] newBytes);

/**
* Writes the new bytes to the output. This operation can only be called once per codegen round.
*/
void write();
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import io.helidon.common.features.HelidonFeatures;
import io.helidon.common.types.TypeName;
import io.helidon.logging.common.LogConfig;
import io.helidon.service.registry.DescriptorMetadata;
import io.helidon.service.registry.DescriptorHandler;
import io.helidon.service.registry.ServiceDiscovery;
import io.helidon.service.registry.ServiceLoader__ServiceDescriptor;
import io.helidon.service.registry.ServiceRegistryConfig;
Expand Down Expand Up @@ -190,7 +190,7 @@ private void processServiceDescriptors(BeforeAnalysisContext context) {

sd.allMetadata()
.stream()
.map(DescriptorMetadata::descriptor)
.map(DescriptorHandler::descriptor)
.filter(it -> it instanceof ServiceLoader__ServiceDescriptor)
.map(it -> (ServiceLoader__ServiceDescriptor) it)
.map(ServiceLoader__ServiceDescriptor::serviceType)
Expand Down
35 changes: 30 additions & 5 deletions service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,39 @@ reflection (the class, and the field).

### Registry file format

The service registry uses a `service.registry` in `META-INF/helidon` directory to store the main metadata of
The service registry uses a `service-registry.json` file in `META-INF/helidon` directory to store the main metadata of
the service. This is to allow proper ordering of services (Service weight is one of the information stored) and
lazy loading of services (which is the approach chosen in the core service registry).

The format is as follows:

```
registry-type:service-descriptor-type:weight(double):contracts(comma separated)
The format is as follows (using `//` to comment sections, not part of the format):

```json
// root is an array of modules (we always generate a single module, but this allows a combined array, i.e. when using shading
[
{
// version of the metadata file, defaults to 1 (and will always default to 1)
"version": 1,
// name of the module
"module": "io.helidon.example",
// all services in this module
"services": [
{
// version of the service descriptor, defaults to 1 (and will always default to 1)
"version": 1,
// core (Service registry) or inject (Service Injection), defaults to core
"type": "inject",
// weight, defaults to 100
"weight": 91.4,
// class of the service descriptor - generated type that contains public constant INSTANCE
"descriptor": "io.helidon.example.ServiceImpl__ServiceDescriptor",
// all contracts this service implements
"contracts": [
"io.helidon.example.ServiceApi"
]
}
]
}
]
```

Example:
Expand Down
8 changes: 8 additions & 0 deletions service/codegen/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,13 @@
<groupId>io.helidon.codegen</groupId>
<artifactId>helidon-codegen-class-model</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.metadata</groupId>
<artifactId>helidon-metadata-hson</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.service</groupId>
<artifactId>helidon-service-metadata</artifactId>
</dependency>
</dependencies>
</project>
Loading