001 /* 002 * Copyright (C) 2006-2007 the original author or 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 * http://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 */ 016 017 package org.codehaus.gmaven.plugin.execute; 018 019 import org.apache.maven.artifact.Artifact; 020 import org.apache.maven.artifact.DependencyResolutionRequiredException; 021 import org.apache.maven.execution.MavenSession; 022 import org.apache.maven.plugin.MojoExecutionException; 023 import org.apache.maven.project.MavenProject; 024 import org.apache.maven.settings.Settings; 025 import org.codehaus.gmaven.common.ArtifactItem; 026 import org.codehaus.gmaven.feature.Component; 027 import org.codehaus.gmaven.feature.Configuration; 028 import org.codehaus.gmaven.plugin.ComponentMojoSupport; 029 import org.codehaus.gmaven.runtime.ScriptExecutor; 030 import org.codehaus.gmaven.runtime.loader.realm.RealmManager; 031 import org.codehaus.gmaven.runtime.support.util.ResourceLoaderImpl; 032 import org.codehaus.gmaven.runtime.util.Callable; 033 import org.codehaus.gmaven.runtime.util.ClassSource; 034 import org.codehaus.gmaven.runtime.util.MagicAttribute; 035 import org.codehaus.gmaven.runtime.util.ResourceLoader; 036 import org.codehaus.plexus.classworlds.realm.ClassRealm; 037 import org.slf4j.Logger; 038 import org.slf4j.LoggerFactory; 039 040 import java.io.File; 041 import java.io.IOException; 042 import java.net.MalformedURLException; 043 import java.net.URL; 044 import java.net.URLClassLoader; 045 import java.util.ArrayList; 046 import java.util.Iterator; 047 import java.util.LinkedHashSet; 048 import java.util.List; 049 import java.util.Map; 050 import java.util.Set; 051 052 /** 053 * Executes a Groovy script. 054 * 055 * @goal execute 056 * @requiresDependencyResolution test 057 * @configurator override 058 * @since 1.0-alpha-1 059 * 060 * @version $Id: ExecuteMojo.java 88 2009-12-11 11:04:25Z user57 $ 061 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a> 062 */ 063 public class ExecuteMojo 064 extends ComponentMojoSupport 065 { 066 /** 067 * The plugin dependencies. 068 * 069 * @parameter expression="${plugin.artifacts}" 070 * @noinspection UnusedDeclaration 071 * @readonly 072 */ 073 private List pluginArtifacts; 074 075 /** 076 * The source of the script to execute. This can be a URL, File or script body. 077 * 078 * @parameter 079 * @required 080 * 081 * @noinspection UnusedDeclaration 082 */ 083 private Source source; 084 085 /** 086 * Additional artifacts to add to the scripts classpath. 087 * 088 * @parameter 089 * 090 * @noinspection UnusedDeclaration,MismatchedReadAndWriteOfArray 091 */ 092 private ArtifactItem[] classpath; 093 094 /** 095 * Path to search for imported scripts. 096 * 097 * @parameter 098 * 099 * @noinspection UnusedDeclaration,MismatchedReadAndWriteOfArray 100 */ 101 private File[] scriptpath; 102 103 /** 104 * A set of default project properties, which the values will be used only if 105 * the project or system does not override. 106 * 107 * @parameter 108 * 109 * @noinspection UnusedDeclaration 110 */ 111 private Map defaults; 112 113 /** 114 * A set of additional project properties. 115 * 116 * @parameter 117 * 118 * @noinspection UnusedDeclaration 119 */ 120 private Map properties; 121 122 /** 123 * Trap assertion errors and rethrow them as execution failures. 124 * 125 * @parameter default-value="true" 126 * 127 * @noinspection UnusedDeclaration 128 */ 129 private boolean trapAssertionErrors; 130 131 /** 132 * Sanatize errors, stripping out Groovy internals. 133 * 134 * @parameter default-value="true" 135 * @since 1.0-beta-3 136 * 137 * @noinspection UnusedDeclaration 138 */ 139 private boolean sanitizeErrors; 140 141 /** 142 * @parameter expression="${session}" 143 * @readonly 144 * @required 145 * 146 * @noinspection UnusedDeclaration 147 */ 148 private MavenSession session; 149 150 /** 151 * @parameter expression="${settings}" 152 * @readonly 153 * @required 154 * 155 * @noinspection UnusedDeclaration 156 */ 157 private Settings settings; 158 159 /** 160 * @component 161 * 162 * @noinspection UnusedDeclaration 163 */ 164 private RealmManager realmManager; 165 166 public ExecuteMojo() { 167 super(ScriptExecutor.KEY); 168 } 169 170 /** 171 * Allow the script to work with every JAR dependency of both the project and plugin, including 172 * optional and provided dependencies. Runtime classpath elements are loaded first, so that 173 * legacy behavior is not modified. Additional elements are added first in the order of 174 * project artifacts, then in the order of plugin artifacts. 175 */ 176 protected List getProjectClasspathElements() throws DependencyResolutionRequiredException { 177 Set results = new LinkedHashSet(); 178 179 for (Iterator i = project.getRuntimeClasspathElements().iterator(); i.hasNext();) { 180 String cpe = (String) i.next(); 181 try { 182 results.add(new File(cpe).getCanonicalPath()); 183 } 184 catch (IOException e) { 185 throw new RuntimeException("Classpath element not found: " + cpe, e); 186 } 187 } 188 189 for (Iterator i = project.getArtifacts().iterator(); i.hasNext();) { 190 Artifact artifact = (Artifact) i.next(); 191 if (artifact.getType().equals("jar") && artifact.getClassifier() == null) { 192 try { 193 results.add(artifact.getFile().getCanonicalPath()); 194 } 195 catch (IOException e) { 196 throw new RuntimeException("Maven artifact file not found: " + artifact.toString(), e); 197 } 198 } 199 } 200 201 for (Iterator i = pluginArtifacts.iterator(); i.hasNext();) { 202 Artifact artifact = (Artifact) i.next(); 203 if (artifact.getType().equals("jar") && artifact.getClassifier() == null) { 204 try { 205 results.add(artifact.getFile().getCanonicalPath()); 206 } 207 catch (IOException e) { 208 throw new RuntimeException("Maven plugin-artifact file not found: " + artifact.toString(), e); 209 } 210 } 211 } 212 return new ArrayList(results); 213 } 214 215 protected ArtifactItem[] getUserClassspathElements() { 216 return classpath; 217 } 218 219 protected void process(final Component component) throws Exception { 220 assert component != null; 221 222 ScriptExecutor executor = (ScriptExecutor)component; 223 224 if (source.configuration.getChildCount() != 0) { 225 throw new MojoExecutionException("Invalid value for 'source' parameter; contains nested elements"); 226 } 227 228 ClassSource classSource = ClassSource.forValue(source.configuration.getValue()); 229 log.debug("Class source: {}", classSource); 230 231 ClassRealm realm = realmManager.createComponentRealm(provider(), createClassPath()); 232 233 ResourceLoader resourceLoader = new MojoResourceLoader(realm, classSource); 234 235 Configuration context = createContext(); 236 237 log.debug("Executing '''{}''' w/context: {}", source, context); 238 239 Object result = executor.execute(classSource, realm, resourceLoader, context); 240 241 log.debug("Result: {}", result); 242 243 realmManager.releaseComponentRealm(realm); 244 } 245 246 private Configuration createContext() { 247 Configuration context = new Configuration(); 248 249 // Expose logging, give it a new logger that has better chances of being configured if needed 250 // would be nice to get the execution id in here... 251 Logger logger = LoggerFactory.getLogger(project.getGroupId() + "." + project.getArtifactId() + ".ExecuteMojo"); 252 context.set("log", logger); 253 254 // Add a custom project to resolve properties 255 MavenProject projectAdapter = new GroovyMavenProjectAdapter(project, session, properties, defaults); 256 context.set("project", projectAdapter); 257 context.set("pom", projectAdapter); 258 259 // Stuff in some other Maven bits 260 context.set("session", session); 261 context.set("settings", settings); 262 263 // Stuff in an Ant helper 264 context.set("ant", MagicAttribute.ANT_BUILDER); 265 266 // Stuff on a fail helper 267 context.set("fail", new FailClosure()); 268 269 return context; 270 } 271 272 // 273 // MojoResourceLoader 274 // 275 276 private class MojoResourceLoader 277 extends ResourceLoaderImpl 278 { 279 private final ClassSource classSource; 280 281 public MojoResourceLoader(final URLClassLoader classLoader, final ClassSource classSource) { 282 super(classLoader); 283 284 assert classSource != null; 285 286 this.classSource = classSource; 287 } 288 289 protected URL resolve(final String className, final ClassLoader classLoader) throws MalformedURLException { 290 assert className != null; 291 assert classLoader != null; 292 293 String resource = toResourceName(className); 294 295 URL url; 296 297 // First check the scriptpath 298 if (scriptpath != null) { 299 for (int i=0; i<scriptpath.length; i++) { 300 assert scriptpath[i] != null; 301 302 File file = new File(scriptpath[i], resource); 303 304 if (file.exists()) { 305 return file.toURI().toURL(); 306 } 307 } 308 } 309 310 // Then look for a resource in the classpath 311 url = classLoader.getResource(resource); 312 313 // Try w/o leading '/'... ??? Seems that when loading resources the '/' prefix messes things up? 314 if (url == null) { 315 if (resource.startsWith("/")) { 316 String tmp = resource.substring(1, resource.length()); 317 318 url = classLoader.getResource(tmp); 319 } 320 } 321 322 if (url == null) { 323 // And finally check for a class defined in a file next to the main script file 324 File script = classSource.file; 325 326 if (script != null) { 327 File file = new File(script.getParentFile(), resource); 328 329 if (file.exists()) { 330 return file.toURI().toURL(); 331 } 332 } 333 } 334 else { 335 return url; 336 } 337 338 return super.resolve(className, classLoader); 339 } 340 } 341 342 // 343 // FailClosure 344 // 345 346 private class FailClosure 347 implements Callable 348 { 349 public Object call(final Object[] args) throws Exception { 350 if (args == null || args.length == 0) { 351 throw new MojoExecutionException("Failed"); 352 } 353 else if (args.length == 1) { 354 if (args[0] instanceof Throwable) { 355 Throwable cause = (Throwable)args[0]; 356 throw new MojoExecutionException(cause.getMessage(), cause); 357 } 358 else { 359 throw new MojoExecutionException(String.valueOf(args[0])); 360 } 361 } 362 else if (args.length == 2) { 363 if (args[1] instanceof Throwable) { 364 throw new MojoExecutionException(String.valueOf(args[0]), (Throwable)args[1]); 365 } 366 else { 367 throw new Error("Invalid arguments to fail(Object, Throwable), second argument must be a Throwable"); 368 } 369 } 370 else { 371 throw new Error("Too many arguments for fail(), expected one of: fail(), fail(Object) or fail(Object, Throwable)"); 372 } 373 } 374 } 375 }