001/* 002 * Copyright 2017-2021 original authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.micronaut.maven.testresources; 017 018import org.apache.maven.AbstractMavenLifecycleParticipant; 019import org.apache.maven.execution.MavenSession; 020import org.apache.maven.model.Build; 021import org.apache.maven.model.Plugin; 022import org.apache.maven.plugin.MojoExecution; 023import org.apache.maven.plugin.PluginParameterExpressionEvaluator; 024import org.apache.maven.project.MavenProject; 025import org.codehaus.plexus.component.annotations.Component; 026import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException; 027import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator; 028import org.codehaus.plexus.logging.Logger; 029import org.codehaus.plexus.util.xml.Xpp3Dom; 030import org.codehaus.plexus.util.xml.Xpp3DomWriter; 031import org.twdata.maven.mojoexecutor.MojoExecutor; 032 033import javax.inject.Inject; 034import java.io.File; 035import java.io.StringWriter; 036import java.util.List; 037import java.util.Map; 038import java.util.concurrent.ConcurrentHashMap; 039import java.util.function.Consumer; 040 041import static io.micronaut.maven.MojoUtils.THIS_PLUGIN; 042import static io.micronaut.maven.testresources.AbstractTestResourcesMojo.CONFIG_PROPERTY_PREFIX; 043import static io.micronaut.maven.testresources.StopTestResourcesServerMojo.MICRONAUT_TEST_RESOURCES_KEEPALIVE; 044 045/** 046 * A lifecycle extension which determines if the test resources server should 047 * be stopped when the build is complete. 048 */ 049@Component(role = AbstractMavenLifecycleParticipant.class, hint = "test-resources") 050public class TestResourcesLifecycleExtension extends AbstractMavenLifecycleParticipant { 051 052 private static final String EXPLICIT_START_SERVICE_GOAL_NAME = "mn:" + StartTestResourcesServerMojo.NAME; 053 private static final String EXPLICIT_STOP_SERVICE_GOAL_NAME = "mn:" + StopTestResourcesServerMojo.NAME; 054 055 private final Map<MavenProject, ExpressionEvaluator> perProjectEvaluator = new ConcurrentHashMap<>(); 056 private final Map<MavenProject, TestResourcesConfiguration> perProjectConfiguration = new ConcurrentHashMap<>(); 057 058 private final Logger logger; 059 060 @Inject 061 @SuppressWarnings("CdiInjectionPointsInspection") 062 public TestResourcesLifecycleExtension(Logger logger) { 063 this.logger = logger; 064 } 065 066 @Override 067 public void afterProjectsRead(MavenSession session) { 068 session.getAllProjects().forEach(currentProject -> { 069 Build build = currentProject.getBuild(); 070 withPlugin(build, plugin -> { 071 ExpressionEvaluator evaluator = perProjectEvaluator.computeIfAbsent(currentProject, p -> initEvaluator(p, session)); 072 TestResourcesConfiguration configuration = perProjectConfiguration.computeIfAbsent(currentProject, mavenProject -> initConfiguration(plugin)); 073 074 boolean enabled = isEnabled(evaluator, configuration); 075 if (enabled) { 076 List<String> goals = session.getGoals(); 077 if (goals.stream().anyMatch(EXPLICIT_START_SERVICE_GOAL_NAME::equals)) { 078 // we need to keep the server alive at the end of the build 079 080 Xpp3Dom flag = new Xpp3Dom(MICRONAUT_TEST_RESOURCES_KEEPALIVE); 081 Xpp3Dom pluginConfiguration = (Xpp3Dom) plugin.getConfiguration(); 082 pluginConfiguration.addChild(flag); 083 flag.setValue("true"); 084 } 085 } 086 }); 087 }); 088 } 089 090 @Override 091 public void afterSessionEnd(MavenSession session) { 092 if (session.getGoals().stream().noneMatch(s -> s.equals(EXPLICIT_START_SERVICE_GOAL_NAME) || s.equals(EXPLICIT_STOP_SERVICE_GOAL_NAME))) { 093 session.getAllProjects().forEach(currentProject -> { 094 Build build = currentProject.getBuild(); 095 withPlugin(build, plugin -> { 096 ExpressionEvaluator evaluator = perProjectEvaluator.computeIfAbsent(currentProject, p -> initEvaluator(p, session)); 097 TestResourcesConfiguration configuration = perProjectConfiguration.computeIfAbsent(currentProject, mavenProject -> initConfiguration(plugin)); 098 boolean enabled = isEnabled(evaluator, configuration); 099 boolean shared = isShared(evaluator, configuration); 100 File buildDirectory = new File(build.getDirectory()); 101 102 var helper = new TestResourcesHelper(session, enabled, shared, buildDirectory); 103 if (shared) { 104 String sharedServerNamespace = findSharedServerNamespace(evaluator, configuration); 105 helper.setSharedServerNamespace(sharedServerNamespace); 106 } 107 try { 108 helper.stop(true); 109 } catch (Exception e) { 110 logger.error(e.getMessage(), e); 111 } 112 }); 113 }); 114 } 115 } 116 117 private String findSharedServerNamespace(ExpressionEvaluator evaluator, TestResourcesConfiguration configuration) { 118 try { 119 String result = (String) evaluator.evaluate("${" + CONFIG_PROPERTY_PREFIX + "namespace" + "}"); 120 if (result != null) { 121 return result; 122 } else if (configuration != null) { 123 return configuration.getSharedServerNamespace(); 124 } 125 } catch (ExpressionEvaluationException e) { 126 return null; 127 } 128 return null; 129 } 130 131 private boolean isShared(ExpressionEvaluator evaluator, TestResourcesConfiguration configuration) { 132 Boolean result = evaluateBooleanProperty(evaluator, CONFIG_PROPERTY_PREFIX + "shared"); 133 if (result != null) { 134 return result; 135 } else if (configuration != null) { 136 return configuration.isShared(); 137 } 138 return false; 139 } 140 141 private boolean isEnabled(ExpressionEvaluator evaluator, TestResourcesConfiguration configuration) { 142 Boolean result = evaluateBooleanProperty(evaluator, CONFIG_PROPERTY_PREFIX + "enabled"); 143 if (result != null) { 144 return result; 145 } else if (configuration != null) { 146 return configuration.isTestResourcesEnabled(); 147 } 148 return false; 149 } 150 151 private TestResourcesConfiguration initConfiguration(Plugin plugin) { 152 var configuration = (Xpp3Dom) plugin.getConfiguration(); 153 if (configuration == null) { 154 configuration = MojoExecutor.configuration(); 155 plugin.setConfiguration(configuration); 156 } 157 var writer = new StringWriter(); 158 Xpp3DomWriter.write(writer, configuration); 159 return parseConfiguration(configuration); 160 } 161 162 private TestResourcesConfiguration parseConfiguration(Xpp3Dom dom) { 163 TestResourcesConfiguration config = null; 164 if (dom != null) { 165 config = new TestResourcesConfiguration(); 166 167 Xpp3Dom testResourcesEnabled = dom.getChild("testResourcesEnabled"); 168 if (testResourcesEnabled != null) { 169 config.setTestResourcesEnabled(Boolean.parseBoolean(testResourcesEnabled.getValue())); 170 } 171 172 Xpp3Dom shared = dom.getChild("shared"); 173 if (shared != null) { 174 config.setShared(Boolean.parseBoolean(shared.getValue())); 175 } 176 177 Xpp3Dom sharedServerNamespace = dom.getChild("sharedServerNamespace"); 178 if (sharedServerNamespace != null) { 179 config.setSharedServerNamespace(sharedServerNamespace.getValue()); 180 } 181 182 Xpp3Dom debugServer = dom.getChild("debugServer"); 183 if (debugServer != null) { 184 config.setShared(Boolean.parseBoolean(debugServer.getValue())); 185 } 186 } 187 return config; 188 } 189 190 private Boolean evaluateBooleanProperty(ExpressionEvaluator evaluator, String property) { 191 try { 192 Object result = evaluator.evaluate("${" + property + "}"); 193 if (result instanceof Boolean b) { 194 return b; 195 } 196 if (result instanceof String s && (s.equals(Boolean.TRUE.toString()) || s.equals(Boolean.FALSE.toString()))) { 197 return Boolean.parseBoolean(s); 198 } 199 } catch (ExpressionEvaluationException e) { 200 return false; 201 } 202 return null; 203 } 204 205 private ExpressionEvaluator initEvaluator(MavenProject currentProject, MavenSession session) { 206 Plugin thisPlugin = currentProject.getPlugin(THIS_PLUGIN); 207 var execution = new MojoExecution(thisPlugin, null, null); 208 MavenProject actualCurrentProject = session.getCurrentProject(); 209 ExpressionEvaluator evaluator; 210 211 // Maven 3: PluginParameterExpressionEvaluator gets the current project from the session: 212 // synchronize in case another thread wants to fetch the real current project in between 213 synchronized (perProjectEvaluator) { 214 session.setCurrentProject(currentProject); 215 evaluator = new PluginParameterExpressionEvaluator(session, execution); 216 session.setCurrentProject(actualCurrentProject); 217 } 218 219 return evaluator; 220 } 221 222 private static void withPlugin(Build build, Consumer<? super Plugin> consumer) { 223 build.getPlugins() 224 .stream() 225 .filter(p -> THIS_PLUGIN.equals(p.getGroupId() + ":" + p.getArtifactId())) 226 .findFirst() 227 .ifPresent(consumer); 228 } 229}