TextApiResource.java

package info.textgrid.services.textapi;

import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;

import info.textgrid.namespaces.middleware.tgsearch.ResultType;
import info.textgrid.services.textapi.apiv1.Actor;
import info.textgrid.services.textapi.apiv1.Collection;
import info.textgrid.services.textapi.apiv1.ContentItem;
import info.textgrid.services.textapi.apiv1.Item;
import info.textgrid.services.textapi.apiv1.Manifest;
import info.textgrid.services.textapi.apiv1.Repository;
import info.textgrid.services.textapi.apiv1.SequenceItem;
import info.textgrid.services.textapi.apiv1.Title;
import info.textgrid.services.textapi.services.SearchClientService;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.runtime.LaunchMode;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;

@Path("/textapi")
public class TextApiResource {

    @Inject
    SearchClientService searchClient;

    @ConfigProperty(name = "textgrid.host")
    String textgridHost;

    @ConfigProperty(name = "service.url")
    String serviceUrl;

    @ConfigProperty(name = "validator.url")
    String validatorUrl;

    @ConfigProperty(name = "quarkus.application.version")
    String version;

    @Inject
    LaunchMode launchMode;

    @Inject
    Template index;

    /**
     * Check if the given format is a valid aggregation format.
     * Accepts: text/tg.aggregation+xml, text/tg.collection+tg.aggregation+xml, text/tg.edition+tg.aggregation+xml
     */
    private boolean isAggregationFormat(String format) {
        return format != null && (
            format.equals("text/tg.aggregation+xml") ||
            format.equals("text/tg.collection+tg.aggregation+xml") ||
            format.equals("text/tg.edition+tg.aggregation+xml")
        );
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/{id}/manifest.json")
    @Operation(summary = "Generate a TextAPI manifest", description = "Use TextGridURI to list all items of an aggregation as TextAPI Items, "
            + "utilizing the aggregator for TEI->HTML transformation")
    public Manifest manifest(
            @Parameter(description = "the TextGridURI to generate a manifest for", example = "textgrid:wp1j.0") @PathParam("id") String id) {

        var meta = searchClient.infoQuery().getMetadata(id);
        if (meta.getResult().isEmpty()) {
            throw new jakarta.ws.rs.WebApplicationException(
                "Resource not found: " + id,
                jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR
            );
        }
        var label = meta.getResult().get(0).getObject().getGeneric().getProvided().getTitle().get(0);
        var format = meta.getResult().get(0).getObject().getGeneric().getProvided().getFormat();

        var r = new Repository().setId(id);
        var m = new Manifest().setId(serviceUrl + "/" + id + "/manifest.json").setLabel(label).addRepository(r);

        // every item of an for aggregations
        if (isAggregationFormat(format)) {
            for (ResultType res : searchClient.navigationQuery().listAggregation(id).getResult()) {
                var s = new SequenceItem()
                        .setId(serviceUrl + "/" + res.getTextgridUri() + "/item.json")
                        .setType("item")
                        .setLabel(res.getObject().getGeneric().getProvided().getTitle().get(0));
                m.addSequenceItem(s);
            }
        } else {
            var s = new SequenceItem().setId(serviceUrl + "/" + id + "/item.json").setLabel(label);
            m.addSequenceItem(s);
        }
        return m;
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/{id}/item.json")
    @Operation(summary = "Generate a TextAPI item", description = "Get aggregator URL for given TextGridUri for TEI -> HTML transformation")
    public Item item(
            @Parameter(description = "textgridUri of a single item (normally listed inside a manifest)", example = "textgrid:wmd3.0") @PathParam("id") String id) {

        var _id = serviceUrl + "/" + id + "/item.json";
        var contentItem = new ContentItem().setUrl(textgridHost + "/1.0/aggregator/html/" + id + "?embedded=true");
        // TODO: lang from textgrid metadata, what if not set? set to und (undetermined)
        // https://de.wikipedia.org/wiki/ISO_639#Spezielle_Kennungen
        var item = new Item().addContent(contentItem).setId(_id).addLang("und");

        return item;
    }

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/info")
    @Operation(summary = "Build info & config of this service")
    public String info() {
        return """
                tg-textapi %s
                textgrid host: %s
                devmode: %s
                """.formatted(version, textgridHost, launchMode.isDevOrTest());
    }

    @GET
    @Produces(MediaType.TEXT_HTML)
    @Path("/")
    @Operation(hidden = true) // hide operation from OpenAPI
    public TemplateInstance index() {
        return index.data("version", version)
                .data("serviceUrl", serviceUrl)
                .data("validatorUrl", validatorUrl)
                .data("devmode", launchMode.isDevOrTest());
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/search/collection.json")
    public Collection search(@QueryParam("q") String query) {
        return searchToCollection(query);
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/search/{q}/collection.json")
    public Collection searchPath(@PathParam("q") String query) {
        return searchToCollection(query);
    }

    Collection searchToCollection(String query) {
        var results = searchClient.searchQuery().setQuery(query).execute();

        var description = "Search results for " + query;
        var title = new Title().setTitle("Query: " + query);
        var collector = new Actor().addRole("collector").setName("you via tgsearch").setId("yoursearch");
        var collection = new Collection()
                .setId("todo")
                .setDescription(description)
                .addTitle(title)
                .addCollector(collector);

        for (ResultType res : results.getResult()) {
            var id = res.getObject().getGeneric().getGenerated().getTextgridUri().getValue();
            var SequenceItem = new SequenceItem()
                    .setId(serviceUrl + "/" + id + "/manifest.json")
                    .setType("manifest")
                    .setLabel(res.getObject().getGeneric().getProvided().getTitle().get(0));
            collection.addSequenceItem(SequenceItem);
        }

        return collection;
    }

}