001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2024-2026 The Enola <https://enola.dev> Authors
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package dev.enola.cli;
019
020import com.google.common.base.Strings;
021import com.google.common.collect.ImmutableList;
022
023import dev.enola.cas.IPFSGatewayResource;
024import dev.enola.common.context.Context;
025import dev.enola.common.io.hashbrown.IntegrityValidatingDelegatingResource;
026import dev.enola.common.io.iri.URIs;
027import dev.enola.common.io.resource.*;
028import dev.enola.data.iri.NamespaceConverter;
029import dev.enola.data.iri.namespace.repo.NamespaceConverterWithRepository;
030import dev.enola.data.iri.namespace.repo.NamespaceRepositoryEnolaDefaults;
031import dev.enola.datatype.DatatypeRepository;
032import dev.enola.model.Datatypes;
033import dev.enola.thing.impl.ImmutableThing;
034import dev.enola.thing.java.ProxyTBF;
035import dev.enola.thing.java.TBF;
036
037import picocli.CommandLine;
038
039import java.nio.file.Paths;
040import java.util.concurrent.Callable;
041
042public abstract class CommandWithResourceProvider implements Callable<Integer> {
043
044    @CommandLine.Option(
045            names = {"--http-scheme"},
046            negatable = true,
047            required = true,
048            defaultValue = "true",
049            fallbackValue = "true",
050            showDefaultValue = CommandLine.Help.Visibility.ALWAYS,
051            description = "Whether external HTTP requests are allowed")
052    boolean http;
053
054    @CommandLine.Option(
055            names = {"--classpath-scheme"},
056            negatable = true,
057            required = true,
058            defaultValue = "true",
059            fallbackValue = "true",
060            showDefaultValue = CommandLine.Help.Visibility.ALWAYS,
061            description =
062                    "Whether classpath:/ resource scheme to access internal JAR content is allowed")
063    boolean classpath;
064
065    @CommandLine.Option(
066            names = {"--file-scheme"},
067            negatable = true,
068            required = true,
069            defaultValue = "true",
070            fallbackValue = "true",
071            showDefaultValue = CommandLine.Help.Visibility.ALWAYS,
072            description = "Whether file:/ resource scheme to access local filesystem is allowed")
073    boolean file;
074
075    @CommandLine.Option(
076            hidden = true,
077            names = {"--test-scheme"},
078            negatable = true,
079            required = true,
080            defaultValue = "false",
081            fallbackValue = "true",
082            showDefaultValue = CommandLine.Help.Visibility.ALWAYS,
083            description = "Whether test:/ resource scheme is allowed")
084    boolean test;
085
086    @CommandLine.Option(
087            names = {"--ipfs-gateway"},
088            // Do NOT specify any defaultValue such as "http://localhost:8080/ipfs/" here,
089            // nor "https://dweb.link/ipfs/" (that's worse, because non-local gateway require trust;
090            // see https://docs.enola.dev/use/fetch/#ipfs).
091            required = false,
092            description = "See https://docs.enola.dev/use/fetch/#ipfs")
093    String ipfsGateway;
094
095    protected ResourceProvider rp;
096
097    @Override
098    public Integer call() throws Exception {
099        run();
100        return 0;
101    }
102
103    protected void run() throws Exception {
104        var builder = ImmutableList.<ResourceProvider>builder();
105        builder.add(new TeapotResource.Provider());
106        builder.add(new DataResource.Provider());
107        builder.add(new MultibaseResource.Provider());
108        builder.add(new EmptyResource.Provider());
109        builder.add(new StringResource.Provider()); // TODO Remove...
110        if (file) {
111            builder.add(new FileResource.Provider());
112            builder.add(new FileDescriptorResource.Provider());
113        }
114        if (http) builder.add(new OkHttpResource.Provider());
115        if (!Strings.isNullOrEmpty(ipfsGateway)) {
116            var httpResourceProvider = new OkHttpResource.Provider();
117            builder.add(new IPFSGatewayResource.Provider(httpResourceProvider, ipfsGateway));
118        }
119        if (test) builder.add(new TestResource.Provider());
120        if (classpath) builder.add(new ClasspathResource.Provider());
121
122        var original = new ResourceProviders(builder.build());
123        rp = new IntegrityValidatingDelegatingResource.Provider(original);
124    }
125
126    protected void setup(Context ctx) {
127        // NB: Singleton are set in class Configuration
128        // TODO Move this entirely into class Configuration...
129
130        ctx.push(ResourceProvider.class, rp);
131        ctx.push(URIs.ContextKeys.BASE, Paths.get("").toUri());
132
133        // TODO Replace DatatypeRepository with store itself, once a Datatype is a Thing
134        DatatypeRepository dtr = Datatypes.DTR;
135        ctx.push(DatatypeRepository.class, dtr);
136
137        // TODO This must be configurable & dynamic... but shouldn't be configured in package cli?
138        var namespaceRepo = NamespaceRepositoryEnolaDefaults.INSTANCE;
139        var namespaceConverter = new NamespaceConverterWithRepository(namespaceRepo);
140        ctx.push(NamespaceConverter.class, namespaceConverter);
141
142        ctx.push(TBF.class, new ProxyTBF(ImmutableThing.FACTORY));
143    }
144}