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