View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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             // Add DI bindings
180             list.addAll(getBindings().getOrDefault(key, Set.of()));
181             // Add Plexus bindings
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                 //noinspection unchecked
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                 // Add DI bindings
210                 list.addAll(getBindings().getOrDefault(elementType, Set.of()));
211                 // Add Plexus bindings
212                 for (var bean : locator.get().locate(toGuiceKey(elementType))) {
213                     if (isPlexusBean(bean)) {
214                         list.add(new BindingToBeanEntry<>(elementType).toBeanEntry(bean));
215                     }
216                 }
217                 //noinspection unchecked
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                 //noinspection unchecked
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                 // ignore
273             }
274             return true;
275         }
276     }
277 }