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}