View Javadoc
1   /*
2    * Copyright 2017-2021 original authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package io.micronaut.build.testresources;
17  
18  import com.fasterxml.jackson.core.JsonProcessingException;
19  import com.fasterxml.jackson.dataformat.xml.XmlMapper;
20  import io.micronaut.build.RunMojo;
21  import org.apache.maven.AbstractMavenLifecycleParticipant;
22  import org.apache.maven.execution.MavenSession;
23  import org.apache.maven.model.Build;
24  import org.apache.maven.model.Dependency;
25  import org.apache.maven.model.Plugin;
26  import org.apache.maven.plugin.MojoExecution;
27  import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
28  import org.apache.maven.project.MavenProject;
29  import org.codehaus.plexus.component.annotations.Component;
30  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
31  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
32  import org.codehaus.plexus.logging.Logger;
33  import org.codehaus.plexus.util.xml.Xpp3Dom;
34  import org.codehaus.plexus.util.xml.Xpp3DomWriter;
35  import org.eclipse.aether.util.artifact.JavaScopes;
36  import org.twdata.maven.mojoexecutor.MojoExecutor;
37  
38  import javax.inject.Inject;
39  import java.io.File;
40  import java.io.StringWriter;
41  import java.io.Writer;
42  import java.util.List;
43  import java.util.Map;
44  import java.util.concurrent.ConcurrentHashMap;
45  import java.util.function.Consumer;
46  
47  import static io.micronaut.build.RunMojo.THIS_PLUGIN;
48  import static io.micronaut.build.services.DependencyResolutionService.TEST_RESOURCES_ARTIFACT_ID_PREFIX;
49  import static io.micronaut.build.services.DependencyResolutionService.TEST_RESOURCES_GROUP;
50  import static io.micronaut.build.testresources.AbstractTestResourcesMojo.CONFIG_PROPERTY_PREFIX;
51  import static io.micronaut.build.testresources.StopTestResourcesServerMojo.MICRONAUT_TEST_RESOURCES_KEEPALIVE;
52  
53  /**
54   * A lifecycle extension which determines if the test resources server should
55   * be stopped when the build is complete.
56   */
57  @Component(role = AbstractMavenLifecycleParticipant.class, hint = "test-resources")
58  public class TestResourcesLifecycleExtension extends AbstractMavenLifecycleParticipant  {
59  
60      private static final String EXPLICIT_START_SERVICE_GOAL_NAME = "mn:" + StartTestResourcesServerMojo.NAME;
61      private static final String EXPLICIT_STOP_SERVICE_GOAL_NAME = "mn:" + StopTestResourcesServerMojo.NAME;
62  
63      private final Map<MavenProject, ExpressionEvaluator> perProjectEvaluator = new ConcurrentHashMap<>();
64      private final Map<MavenProject, TestResourcesConfiguration> perProjectConfiguration = new ConcurrentHashMap<>();
65  
66      private final Logger logger;
67      private final XmlMapper mapper;
68  
69      @Inject
70      @SuppressWarnings("CdiInjectionPointsInspection")
71      public TestResourcesLifecycleExtension(Logger logger, XmlMapper mapper) {
72          this.logger = logger;
73          this.mapper = mapper;
74      }
75  
76      @Override
77      public void afterProjectsRead(MavenSession session) {
78          session.getAllProjects().forEach(currentProject -> {
79              Build build = currentProject.getBuild();
80              withPlugin(build, plugin -> {
81                  ExpressionEvaluator evaluator = perProjectEvaluator.computeIfAbsent(currentProject, p -> initEvaluator(p, session));
82                  TestResourcesConfiguration configuration = perProjectConfiguration.computeIfAbsent(currentProject, mavenProject -> initConfiguration(plugin));
83  
84                  boolean enabled = isEnabled(evaluator, configuration);
85                  if (enabled) {
86                      List<String> goals = session.getGoals();
87                      if (goals.stream().anyMatch(EXPLICIT_START_SERVICE_GOAL_NAME::equals)) {
88                          // we need to keep the server alive at the end of the build
89  
90                          Xpp3Dom flag = new Xpp3Dom(MICRONAUT_TEST_RESOURCES_KEEPALIVE);
91                          Xpp3Dom pluginConfiguration = (Xpp3Dom) plugin.getConfiguration();
92                          pluginConfiguration.addChild(flag);
93                          flag.setValue("true");
94                      }
95                      Dependency clientDependency = new Dependency();
96                      clientDependency.setGroupId(TEST_RESOURCES_GROUP);
97                      clientDependency.setArtifactId(TEST_RESOURCES_ARTIFACT_ID_PREFIX + "client");
98                      clientDependency.setVersion(String.valueOf(currentProject.getProperties().get(CONFIG_PROPERTY_PREFIX + "version")));
99                      clientDependency.setScope(JavaScopes.TEST);
100                     currentProject.getDependencies().add(clientDependency);
101                 }
102             });
103         });
104     }
105 
106     @Override
107     public void afterSessionEnd(MavenSession session) {
108         if (session.getGoals().stream().noneMatch(s -> s.equals(EXPLICIT_START_SERVICE_GOAL_NAME) || s.equals(EXPLICIT_STOP_SERVICE_GOAL_NAME))) {
109             session.getAllProjects().forEach(currentProject -> {
110                 Build build = currentProject.getBuild();
111                 withPlugin(build, plugin -> {
112                     ExpressionEvaluator evaluator = perProjectEvaluator.computeIfAbsent(currentProject, p -> initEvaluator(p, session));
113                     TestResourcesConfiguration configuration = perProjectConfiguration.computeIfAbsent(currentProject, mavenProject -> initConfiguration(plugin));
114                     boolean enabled = isEnabled(evaluator, configuration);
115                     boolean keepAlive = isKeepAlive(evaluator, configuration);
116                     boolean shared = isShared(evaluator, configuration);
117                     File buildDirectory = new File(build.getDirectory());
118 
119                     TestResourcesHelper helper = new TestResourcesHelper(enabled, keepAlive, shared, buildDirectory);
120                     if (shared) {
121                         String sharedServerNamespace = findSharedServerNamespace(evaluator, configuration);
122                         helper.setSharedServerNamespace(sharedServerNamespace);
123                     }
124                     try {
125                         helper.stop();
126                     } catch (Exception e) {
127                         logger.error(e.getMessage(), e);
128                     }
129                 });
130             });
131         }
132     }
133 
134     private String findSharedServerNamespace(ExpressionEvaluator evaluator, TestResourcesConfiguration configuration) {
135         try {
136             String result = (String) evaluator.evaluate("${" + CONFIG_PROPERTY_PREFIX + "namespace" + "}");
137             if (result != null) {
138                 return result;
139             } else if (configuration != null) {
140                 return configuration.getSharedServerNamespace();
141             }
142         } catch (ExpressionEvaluationException e) {
143             return null;
144         }
145         return null;
146     }
147 
148     private boolean isShared(ExpressionEvaluator evaluator, TestResourcesConfiguration configuration) {
149         Boolean result = evaluateBooleanProperty(evaluator, CONFIG_PROPERTY_PREFIX + "shared");
150         if (result != null) {
151             return result;
152         } else if (configuration != null) {
153             return configuration.isShared();
154         }
155         return false;
156     }
157 
158     private boolean isKeepAlive(ExpressionEvaluator evaluator, TestResourcesConfiguration configuration) {
159         Boolean result = evaluateBooleanProperty(evaluator, MICRONAUT_TEST_RESOURCES_KEEPALIVE);
160         if (result != null) {
161             return result;
162         } else if (configuration != null) {
163             return configuration.isKeepAlive();
164         }
165         return false;
166     }
167 
168     private boolean isEnabled(ExpressionEvaluator evaluator, TestResourcesConfiguration configuration) {
169         Boolean result = evaluateBooleanProperty(evaluator, CONFIG_PROPERTY_PREFIX + "enabled");
170         if (result != null) {
171             return result;
172         } else if (configuration != null) {
173             return configuration.isTestResourcesEnabled();
174         }
175         return false;
176     }
177 
178     private TestResourcesConfiguration initConfiguration(Plugin plugin) {
179         Xpp3Dom configuration = (Xpp3Dom) plugin.getConfiguration();
180         if (configuration == null) {
181             configuration = MojoExecutor.configuration();
182             plugin.setConfiguration(configuration);
183         }
184         Writer writer = new StringWriter();
185         Xpp3DomWriter.write(writer, configuration);
186         try {
187             return mapper.readValue(writer.toString(), TestResourcesConfiguration.class);
188         } catch (JsonProcessingException e) {
189             logger.error(e.getMessage(), e);
190             return new TestResourcesConfiguration();
191         }
192     }
193 
194     private Boolean evaluateBooleanProperty(ExpressionEvaluator evaluator, String property) {
195         try {
196             Object result = evaluator.evaluate("${" + property + "}");
197             if (result instanceof Boolean) {
198                 return (Boolean) result;
199             } else if (result instanceof String && (result.equals(Boolean.TRUE.toString()) || result.equals(Boolean.FALSE.toString()))) {
200                 return Boolean.parseBoolean((String) result);
201             }
202         } catch (ExpressionEvaluationException e) {
203             return false;
204         }
205         return null;
206     }
207 
208     private ExpressionEvaluator initEvaluator(MavenProject currentProject, MavenSession session) {
209         Plugin thisPlugin = currentProject.getPlugin(THIS_PLUGIN);
210         MojoExecution execution = new MojoExecution(thisPlugin, null, null);
211         MavenProject actualCurrentProject = session.getCurrentProject();
212         ExpressionEvaluator evaluator;
213 
214         // Maven 3: PluginParameterExpressionEvaluator gets the current project from the session:
215         // synchronize in case another thread wants to fetch the real current project in between
216         synchronized (perProjectEvaluator) {
217             session.setCurrentProject(currentProject);
218             evaluator = new PluginParameterExpressionEvaluator(session, execution);
219             session.setCurrentProject(actualCurrentProject);
220         }
221 
222         return evaluator;
223     }
224 
225     private static void withPlugin(Build build, Consumer<? super Plugin> consumer) {
226         build.getPlugins()
227                 .stream()
228                 .filter(p -> RunMojo.THIS_PLUGIN.equals(p.getGroupId() + ":" + p.getArtifactId()))
229                 .findFirst()
230                 .ifPresent(consumer);
231     }
232 }