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.io.BufferedReader;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.InputStreamReader;
28  import java.lang.annotation.Annotation;
29  import java.net.URL;
30  import java.nio.charset.StandardCharsets;
31  import java.util.ArrayList;
32  import java.util.Comparator;
33  import java.util.HashSet;
34  import java.util.Iterator;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.function.Supplier;
39  import java.util.stream.Collectors;
40  
41  import com.google.inject.AbstractModule;
42  import com.google.inject.binder.AnnotatedBindingBuilder;
43  import com.google.inject.name.Names;
44  import org.apache.maven.api.di.MojoExecutionScoped;
45  import org.apache.maven.api.di.SessionScoped;
46  import org.apache.maven.api.services.MavenException;
47  import org.apache.maven.di.Injector;
48  import org.apache.maven.di.Key;
49  import org.apache.maven.di.impl.Binding;
50  import org.apache.maven.di.impl.DIException;
51  import org.apache.maven.di.impl.InjectorImpl;
52  import org.apache.maven.execution.scope.internal.MojoExecutionScope;
53  import org.apache.maven.session.scope.internal.SessionScope;
54  import org.codehaus.plexus.PlexusContainer;
55  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
56  
57  @Named
58  public class SisuDiBridgeModule extends AbstractModule {
59  
60      InjectorImpl injector;
61      final Set<String> loaded = new HashSet<>();
62  
63      @Override
64      protected void configure() {
65          Provider<PlexusContainer> containerProvider = getProvider(PlexusContainer.class);
66  
67          injector = new InjectorImpl() {
68              @Override
69              public <Q> Supplier<Q> getCompiledBinding(Key<Q> key) {
70                  Set<Binding<Q>> res = getBindings(key);
71                  if (res != null && !res.isEmpty()) {
72                      List<Binding<Q>> bindingList = new ArrayList<>(res);
73                      Comparator<Binding<Q>> comparing = Comparator.comparing(Binding::getPriority);
74                      bindingList.sort(comparing.reversed());
75                      Binding<Q> binding = bindingList.get(0);
76                      return compile(binding);
77                  }
78                  if (key.getRawType() == List.class) {
79                      Set<Binding<Object>> res2 = getBindings(key.getTypeParameter(0));
80                      Set<Binding<Object>> res3 = res2 != null ? new HashSet<>(res2) : new HashSet<>();
81                      try {
82                          List<Object> l = containerProvider
83                                  .get()
84                                  .lookupList(key.getTypeParameter(0).getRawType());
85                          l.forEach(o -> res3.add(new Binding.BindingToInstance<>(o)));
86                      } catch (Throwable e) {
87                          // ignore
88                          e.printStackTrace();
89                      }
90                      List<Supplier<Object>> list =
91                              res3.stream().map(this::compile).collect(Collectors.toList());
92                      //noinspection unchecked
93                      return () -> (Q) list(list);
94                  }
95                  if (key.getRawType() == Map.class) {
96                      Key<?> k = key.getTypeParameter(0);
97                      Key<Object> v = key.getTypeParameter(1);
98                      if (k.getRawType() == String.class) {
99                          Set<Binding<Object>> res2 = getBindings(v);
100                         Set<Binding<Object>> res3 = res2 != null ? new HashSet<>(res2) : new HashSet<>();
101                         Map<String, Supplier<Object>> map = res3.stream()
102                                 .filter(b -> b.getOriginalKey() == null
103                                         || b.getOriginalKey().getQualifier() == null
104                                         || b.getOriginalKey().getQualifier() instanceof String)
105                                 .collect(Collectors.toMap(
106                                         b -> (String)
107                                                 (b.getOriginalKey() != null
108                                                         ? b.getOriginalKey().getQualifier()
109                                                         : null),
110                                         this::compile));
111                         //noinspection unchecked
112                         return () -> (Q) map(map);
113                     }
114                 }
115                 try {
116                     Q t = containerProvider.get().lookup(key.getRawType());
117                     return compile(new Binding.BindingToInstance<>(t));
118                 } catch (Throwable e) {
119                     // ignore
120                     e.printStackTrace();
121                 }
122                 throw new DIException("No binding to construct an instance for key "
123                         + key.getDisplayString() + ".  Existing bindings:\n"
124                         + getBoundKeys().stream()
125                                 .map(Key::toString)
126                                 .map(String::trim)
127                                 .sorted()
128                                 .distinct()
129                                 .collect(Collectors.joining("\n - ", " - ", "")));
130             }
131         };
132         injector.bindScope(SessionScoped.class, () -> {
133             try {
134                 return containerProvider.get().lookup(SessionScope.class);
135             } catch (ComponentLookupException e) {
136                 throw new RuntimeException(e);
137             }
138         });
139         injector.bindScope(MojoExecutionScoped.class, () -> {
140             try {
141                 return containerProvider.get().lookup(MojoExecutionScope.class);
142             } catch (ComponentLookupException e) {
143                 throw new RuntimeException(e);
144             }
145         });
146         injector.bindInstance(Injector.class, injector);
147         bind(Injector.class).toInstance(injector);
148         bind(SisuDiBridgeModule.class).toInstance(this);
149         ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
150         if (classLoader == null) {
151             classLoader = getClass().getClassLoader();
152         }
153         loadFromClassLoader(classLoader);
154         injector.getBindings().keySet().stream()
155                 .filter(k -> k.getQualifier() != null)
156                 .sorted(Comparator.comparing(k -> k.getRawType().getName()))
157                 .distinct()
158                 .forEach(key -> {
159                     Class<?> clazz = key.getRawType();
160                     Class<Object> itf = (clazz.isInterface()
161                             ? null
162                             : (Class<Object>) (clazz.getInterfaces().length > 0 ? clazz.getInterfaces()[0] : clazz));
163                     if (itf != null) {
164                         AnnotatedBindingBuilder<Object> binder = bind(itf);
165                         if (key.getQualifier() instanceof String s && !s.isEmpty()) {
166                             binder.annotatedWith(Names.named(s));
167                         } else if (key.getQualifier() instanceof Annotation a) {
168                             binder.annotatedWith(a);
169                         }
170                         binder.toProvider(() -> injector.getInstance(clazz));
171                     }
172                 });
173     }
174 
175     public void loadFromClassLoader(ClassLoader classLoader) {
176         try {
177             for (Iterator<URL> it = classLoader
178                             .getResources("META-INF/maven/org.apache.maven.api.di.Inject")
179                             .asIterator();
180                     it.hasNext(); ) {
181                 URL url = it.next();
182                 if (loaded.add(url.toExternalForm())) {
183                     List<String> lines;
184                     try (InputStream is = url.openStream();
185                             BufferedReader reader =
186                                     new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
187                         lines = reader.lines()
188                                 .map(String::trim)
189                                 .filter(s -> !s.isEmpty() && !s.startsWith("#"))
190                                 .toList();
191                     }
192                     for (String className : lines) {
193                         try {
194                             Class<?> clazz = classLoader.loadClass(className);
195                             injector.bindImplicit(clazz);
196                         } catch (ClassNotFoundException e) {
197                             // ignore
198                             e.printStackTrace();
199                         }
200                     }
201                 }
202             }
203         } catch (IOException e) {
204             throw new MavenException(e);
205         }
206     }
207 }