001package io.jstach.jstachio.spi; 002 003import java.util.ArrayList; 004import java.util.List; 005import java.util.Optional; 006import java.util.ServiceLoader; 007import java.util.stream.Stream; 008import java.util.stream.StreamSupport; 009 010import org.eclipse.jdt.annotation.Nullable; 011 012/** 013 * A container that will hold all resolved {@link JStachioExtension}s and consolidate them 014 * to a single instances of various services. 015 * 016 * @apiNote While this interface looks similar to {@link JStachioExtension} it is not an 017 * extension but rather an immutable bean like container. The methods are purposely java 018 * bean style (which is not the default in JStachio as JStachio prefers newer record like 019 * accessor method names) to support as many frameworks as possible. 020 * @author agentgt 021 */ 022public interface JStachioExtensions { 023 024 /** 025 * A marker interface used for JStachio implementations that provide access to 026 * extensions. 027 * 028 * @author agentgt 029 * 030 */ 031 public interface Provider { 032 033 /** 034 * The available extensions. 035 * @return The avaiable resolved extensions. 036 */ 037 public JStachioExtensions extensions(); 038 039 } 040 041 /** 042 * Resolve from an iterable of extensions that usually come from some discovery 043 * mechanism like the {@link ServiceLoader} or a DI framework. <em>The order of the 044 * extensions is important and primacy order takes precedence!</em> 045 * @param extensions found extensions. 046 * @return bean like container of services. 047 */ 048 public static JStachioExtensions of(Iterable<? extends JStachioExtension> extensions) { 049 return of(StreamSupport.stream(extensions.spliterator(), false)); 050 } 051 052 /** 053 * Resolve from a stream of extensions that usually come from some discovery mechanism 054 * like the {@link ServiceLoader} or a DI framework. <em>The order of the extensions 055 * is important and primacy order takes precedence!</em> 056 * @param extensions found extensions. 057 * @return bean like container of services. 058 */ 059 public static JStachioExtensions of(Stream<? extends JStachioExtension> extensions) { 060 return DefaultJStachioExtensions.of(extensions); 061 } 062 063 /** 064 * Resolves extensions from the {@link ServiceLoader} with {@link JStachioExtension} 065 * as the SPI. 066 * @return jstachio extensions found by the ServiceLoader 067 */ 068 public static JStachioExtensions of() { 069 Iterable<JStachioExtension> it = ServiceLoader.load(JStachioExtension.class); 070 return of(it); 071 } 072 073 /** 074 * Composite Config where the first config that returns a nonnull for 075 * {@link JStachioConfig#getProperty(String)} is used. 076 * @return config 077 */ 078 JStachioConfig getConfig(); 079 080 /** 081 * Composite Filter where the ordering of the filter is based on a combination of 082 * {@link JStachioFilter#order()} first and then the order in the iterable passed to 083 * {@link #of(Iterable)}. 084 * @return filter 085 */ 086 JStachioFilter getFilter(); 087 088 /** 089 * Composite Template finder where the first template finder that finds a template is 090 * used. 091 * @return template finder 092 */ 093 JStachioTemplateFinder getTemplateFinder(); 094 095 /** 096 * The orignal contained extensions excluding the composites. 097 * @return found services 098 */ 099 List<JStachioExtension> getExtensions(); 100 101 /** 102 * Finds a specific implementation using {@link Class#isAssignableFrom(Class)}. 103 * @param <T> the implementation type 104 * @param c the implementation type. 105 * @return an implementation if found 106 */ 107 default <T extends JStachioExtension> Optional<T> findExtension(Class<T> c) { 108 return getExtensions().stream().filter(s -> c.isAssignableFrom(s.getClass())).map(c::cast).findFirst(); 109 } 110 111} 112 113class DefaultJStachioExtensions implements JStachioExtensions { 114 115 private final List<JStachioExtension> services; 116 117 private final JStachioConfig config; 118 119 private final JStachioFilter filter; 120 121 private final JStachioTemplateFinder templateFinder; 122 123 private DefaultJStachioExtensions(List<JStachioExtension> services, JStachioConfig config, JStachioFilter filter, 124 JStachioTemplateFinder templateFinder) { 125 super(); 126 this.services = services; 127 this.config = config; 128 this.filter = filter; 129 this.templateFinder = templateFinder; 130 } 131 132 /** 133 * Create a container from service providers. 134 * @param it services 135 * @return bean like container of services. 136 */ 137 static JStachioExtensions of(Stream<? extends JStachioExtension> it) { 138 List<JStachioExtensionProvider> svs = new ArrayList<>(); 139 it.forEach(s -> svs.add(JStachioExtensionProvider.of(s))); 140 141 List<JStachioConfig> configs = new ArrayList<>(); 142 List<JStachioFilter> filters = new ArrayList<>(); 143 List<JStachioTemplateFinder> finders = new ArrayList<>(); 144 145 for (var sv : svs) { 146 var c = sv.provideConfig(); 147 if (c != null) { 148 configs.add(c); 149 } 150 } 151 JStachioConfig config = CompositeConfig.of(configs); 152 153 for (var sv : svs) { 154 sv.init(config); 155 @Nullable 156 JStachioFilter filt = sv.provideFilter(); 157 if (filt != null) { 158 filters.add(filt); 159 } 160 @Nullable 161 JStachioTemplateFinder find = sv.provideTemplateFinder(); 162 if (find != null) { 163 finders.add(find); 164 } 165 } 166 JStachioFilter filter = JStachioFilter.compose(filters); 167 if (finders.isEmpty()) { 168 finders.add( 169 JStachioTemplateFinder.cachedTemplateFinder(JStachioTemplateFinder.defaultTemplateFinder(config))); 170 } 171 JStachioTemplateFinder templateFinder = CompositeTemplateFinder.of(finders); 172 return new DefaultJStachioExtensions(List.copyOf(svs), config, filter, templateFinder); 173 } 174 175 /** 176 * Composite Config 177 * @return config 178 */ 179 @Override 180 public JStachioConfig getConfig() { 181 return config; 182 } 183 184 /** 185 * Composite Filter 186 * @return filter 187 */ 188 @Override 189 public JStachioFilter getFilter() { 190 return filter; 191 } 192 193 /** 194 * Composite Template finder 195 * @return template finder 196 */ 197 @Override 198 public JStachioTemplateFinder getTemplateFinder() { 199 return templateFinder; 200 } 201 202 /** 203 * Services 204 * @return found services 205 */ 206 @Override 207 public List<JStachioExtension> getExtensions() { 208 return services; 209 } 210 211}