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