1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.internal.impl;
20
21 import javax.inject.Named;
22 import javax.inject.Provider;
23
24 import java.lang.annotation.Annotation;
25 import java.lang.reflect.Field;
26 import java.util.ArrayList;
27 import java.util.Comparator;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.function.Function;
33 import java.util.function.Supplier;
34 import java.util.stream.Collectors;
35
36 import com.google.inject.AbstractModule;
37 import com.google.inject.Binder;
38 import com.google.inject.name.Names;
39 import com.google.inject.spi.ProviderInstanceBinding;
40 import org.apache.maven.api.di.MojoExecutionScoped;
41 import org.apache.maven.api.di.SessionScoped;
42 import org.apache.maven.di.Injector;
43 import org.apache.maven.di.Key;
44 import org.apache.maven.di.Scope;
45 import org.apache.maven.di.impl.Binding;
46 import org.apache.maven.di.impl.DIException;
47 import org.apache.maven.di.impl.Dependency;
48 import org.apache.maven.di.impl.InjectorImpl;
49 import org.apache.maven.execution.scope.internal.MojoExecutionScope;
50 import org.apache.maven.session.scope.internal.SessionScope;
51 import org.codehaus.plexus.PlexusContainer;
52 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
53 import org.eclipse.sisu.BeanEntry;
54 import org.eclipse.sisu.inject.BeanLocator;
55
56 @Named
57 public class SisuDiBridgeModule extends AbstractModule {
58
59 protected final boolean discover;
60 protected InjectorImpl injector;
61
62 public SisuDiBridgeModule() {
63 this(true);
64 }
65
66 public SisuDiBridgeModule(boolean discover) {
67 this.discover = discover;
68 }
69
70 @Override
71 protected void configure() {
72 Provider<PlexusContainer> containerProvider = getProvider(PlexusContainer.class);
73 Provider<BeanLocator> beanLocatorProvider = getProvider(BeanLocator.class);
74 injector = new BridgeInjectorImpl(beanLocatorProvider, binder());
75 bindScope(injector, containerProvider, SessionScoped.class, SessionScope.class);
76 bindScope(injector, containerProvider, MojoExecutionScoped.class, MojoExecutionScope.class);
77 injector.bindInstance(Injector.class, injector);
78 bind(Injector.class).toInstance(injector);
79 bind(SisuDiBridgeModule.class).toInstance(this);
80 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
81 if (classLoader == null) {
82 classLoader = getClass().getClassLoader();
83 }
84 if (discover) {
85 injector.discover(classLoader);
86 }
87 }
88
89 private void bindScope(
90 InjectorImpl injector,
91 Provider<PlexusContainer> containerProvider,
92 Class<? extends Annotation> sa,
93 Class<? extends Scope> ss) {
94 injector.bindScope(sa, () -> {
95 try {
96 return containerProvider.get().lookup(ss);
97 } catch (ComponentLookupException e) {
98 throw new RuntimeException(e);
99 }
100 });
101 }
102
103 static class BridgeInjectorImpl extends InjectorImpl {
104 final Provider<BeanLocator> locator;
105 final Binder binder;
106
107 BridgeInjectorImpl(Provider<BeanLocator> locator, Binder binder) {
108 this.locator = locator;
109 this.binder = binder;
110 }
111
112 @Override
113 protected <U> Injector bind(Key<U> key, Binding<U> binding) {
114 super.bind(key, binding);
115 if (key.getQualifier() != null) {
116 com.google.inject.Key<U> k = toGuiceKey(key);
117 this.binder.bind(k).toProvider(new BridgeProvider<>(binding));
118 }
119 return this;
120 }
121
122 @SuppressWarnings("unchecked")
123 private static <U> com.google.inject.Key<U> toGuiceKey(Key<U> key) {
124 if (key.getQualifier() instanceof String s) {
125 return (com.google.inject.Key<U>) com.google.inject.Key.get(key.getType(), Names.named(s));
126 } else if (key.getQualifier() instanceof Annotation a) {
127 return (com.google.inject.Key<U>) com.google.inject.Key.get(key.getType(), a);
128 } else {
129 return (com.google.inject.Key<U>) com.google.inject.Key.get(key.getType());
130 }
131 }
132
133 static class BindingToBeanEntry<T> extends Binding<T> {
134 private BeanEntry<Annotation, T> beanEntry;
135
136 BindingToBeanEntry(Key<T> elementType) {
137 super(elementType, Set.of());
138 }
139
140 public BindingToBeanEntry<T> toBeanEntry(BeanEntry<Annotation, T> beanEntry) {
141 this.beanEntry = beanEntry;
142 return this;
143 }
144
145 @Override
146 public Supplier<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
147 return beanEntry.getProvider()::get;
148 }
149 }
150
151 class BridgeProvider<T> implements Provider<T> {
152 final Binding<T> binding;
153
154 BridgeProvider(Binding<T> binding) {
155 this.binding = binding;
156 }
157
158 @Override
159 public T get() {
160 return compile(binding).get();
161 }
162 }
163
164 @Override
165 public <Q> Supplier<Q> getCompiledBinding(Dependency<Q> dep) {
166 Key<Q> key = dep.key();
167 Class<Q> rawType = key.getRawType();
168 if (rawType == List.class) {
169 return getListSupplier(key);
170 } else if (rawType == Map.class) {
171 return getMapSupplier(key);
172 } else {
173 return getBeanSupplier(dep, key);
174 }
175 }
176
177 private <Q> Supplier<Q> getBeanSupplier(Dependency<Q> dep, Key<Q> key) {
178 List<Binding<?>> list = new ArrayList<>();
179
180 list.addAll(getBindings().getOrDefault(key, Set.of()));
181
182 for (var bean : locator.get().locate(toGuiceKey(key))) {
183 if (isPlexusBean(bean)) {
184 list.add(new BindingToBeanEntry<>(key).toBeanEntry(bean));
185 }
186 }
187 if (!list.isEmpty()) {
188 list.sort(getBindingComparator());
189
190 return () -> (Q) getInstance(list.iterator().next());
191 } else if (dep.optional()) {
192 return () -> null;
193 } else {
194 throw new DIException("No binding to construct an instance for key "
195 + key.getDisplayString() + ". Existing bindings:\n"
196 + getBoundKeys().stream()
197 .map(Key::toString)
198 .map(String::trim)
199 .sorted()
200 .distinct()
201 .collect(Collectors.joining("\n - ", " - ", "")));
202 }
203 }
204
205 private <Q> Supplier<Q> getListSupplier(Key<Q> key) {
206 Key<Object> elementType = key.getTypeParameter(0);
207 return () -> {
208 List<Binding<?>> list = new ArrayList<>();
209
210 list.addAll(getBindings().getOrDefault(elementType, Set.of()));
211
212 for (var bean : locator.get().locate(toGuiceKey(elementType))) {
213 if (isPlexusBean(bean)) {
214 list.add(new BindingToBeanEntry<>(elementType).toBeanEntry(bean));
215 }
216 }
217
218 return (Q) list(list.stream().sorted(getBindingComparator()).toList(), this::getInstance);
219 };
220 }
221
222 private <Q> Supplier<Q> getMapSupplier(Key<Q> key) {
223 Key<?> keyType = key.getTypeParameter(0);
224 Key<Object> valueType = key.getTypeParameter(1);
225 if (keyType.getRawType() != String.class) {
226 throw new DIException("Only String keys are supported for maps: " + key);
227 }
228 return () -> {
229 var comparator = getBindingComparator();
230 Map<String, Binding<?>> map = new HashMap<>();
231 for (Binding<?> b : getBindings().getOrDefault(valueType, Set.of())) {
232 String name =
233 b.getOriginalKey() != null && b.getOriginalKey().getQualifier() instanceof String s
234 ? s
235 : "";
236 map.compute(name, (n, ob) -> ob == null || comparator.compare(ob, b) < 0 ? b : ob);
237 }
238 for (var bean : locator.get().locate(toGuiceKey(valueType))) {
239 if (isPlexusBean(bean)) {
240 Binding<?> b = new BindingToBeanEntry<>(valueType)
241 .toBeanEntry(bean)
242 .prioritize(bean.getRank());
243 String name = bean.getKey() instanceof com.google.inject.name.Named n ? n.value() : "";
244 map.compute(name, (n, ob) -> ob == null || ob.getPriority() < b.getPriority() ? b : ob);
245 }
246 }
247
248 return (Q) map(map, this::getInstance);
249 };
250 }
251
252 private <Q> Q getInstance(Binding<Q> binding) {
253 return compile(binding).get();
254 }
255
256 private static Comparator<Binding<?>> getBindingComparator() {
257 Comparator<Binding<?>> comparing = Comparator.comparing(Binding::getPriority);
258 return comparing.reversed();
259 }
260
261 private <T> boolean isPlexusBean(BeanEntry<Annotation, T> entry) {
262 try {
263 if ("org.eclipse.sisu.inject.LazyBeanEntry"
264 .equals(entry.getClass().getName())) {
265 Field f = entry.getClass().getDeclaredField("binding");
266 f.setAccessible(true);
267 Object b = f.get(entry);
268 return !(b instanceof ProviderInstanceBinding<?> pib)
269 || !(pib.getUserSuppliedProvider() instanceof BridgeProvider<?>);
270 }
271 } catch (Exception e) {
272
273 }
274 return true;
275 }
276 }
277 }