I'm writing a Maven plugin whose job is to delegate to a core module who needs to read files from the project classpath (i.e. the project declaring the plugin as a plugin dependency).
However, from what I understand, Maven plugin comes with its own classpath, thus leading all my Class#getResourceAsStream in my core module calls to returning null.
Is there a way to include the project classpath elements in the plugin one?
OK, the issue was due to a leading slash. Once removed, the following code retrieves the MavenProject instance (you need to add maven-artifact and maven-project to your POM):
public static ClassLoader getClassLoader(MavenProject project) throws DependencyResolutionRequiredException, MalformedURLException {
List<String> classPathElements = compileClassPathElements(project);
List<URL> classpathElementUrls = new ArrayList<>(classPathElements.size());
for (String classPathElement : classPathElements) {
classpathElementUrls.add(new File(classPathElement).toURI().toURL());
}
return new URLClassLoader(
classpathElementUrls.toArray(new URL[classpathElementUrls.size()]),
Thread.currentThread().getContextClassLoader()
);
}
private static List<String> compileClassPathElements(MavenProject project) throws DependencyResolutionRequiredException {
return newArrayList(project.getCompileClasspathElements());
}
Related
I've created a Gradle plugin which scans the class files in my project and find which ones have an annotation.
I got it to work and compiled it. Now when I run the task in my project, instead of scanning the classes of the project that I'm in and runs the task on it, it scans the class files of the plugin project itself. What am I doing wrong?
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
try {
ClassPath classpath = ClassPath.from(loader); // scans the class path used by classloader
log.info("classPath = {}", classpath);
for (ClassPath.ClassInfo classInfo : classpath.getTopLevelClassesRecursive(packageName)) {
log.info("classInfo={}", classInfo);
Class<?> clazz = classInfo.load();
if (clazz.isAnnotationPresent(RestController.class)) {
Method[] methods = clazz.getMethods();
for (Method method : methods) {
doSomething(clazz, method);
}
}
}
} catch (IOException | NoSuchFieldException | IllegalAccessException ex) {
String errorMsg = new StringBuilder("Unable to generate ").append(propertiesFileName).append(" file.").toString();
log.error(errorMsg, ex);
throw ex;
}
This cannot work the way you expect:
Your project class files are, by default, not loaded by Gradle as part of building your project. They will be produced by compilation, loaded by test execution, etc ... but normally not as part of Gradle executing tasks.
When your plugin reaches out to classloaders, it is in the managed Gradle world, which has its own tricks with classloaders. So you would have to understand which tricks are pulled ... but given 1, that would still not work.
Given that what you are attempting looks a lot like annotation processing, you should investigate that way of handling your needs.
It took a lot of digging in Gradle forums, and still, I'm experiencing many issues with that, but this is the best solution I could found:
URL[] urls;
List<URL> listOfURL = new ArrayList<>();
SourceSetContainer ssc = getProject().getConvention().getPlugin(JavaPluginConvention.class).getSourceSets();
File classesDir = ssc.getByName("main").getOutput().getClassesDir();
listOfURL.add(classesDir.toURI().toURL());
urls = listOfURL.toArray(new URL[0]);
final ClassLoader loader = new URLClassLoader(urls);
I have my custom maven plugin, which has to run tests programmatically on a test phase for example. So I have something like that
#Mojo(name = "aggregate", requiresDependencyResolution = ResolutionScope.RUNTIME)
public class AcceptanceTestMojo extends AbstractMojo {
#Override
public void execute() throws MojoExecutionException, MojoFailureException {
TestExecutor testExecutor = new TestExecutor();
testExecutor.setTestClasses(new Class[]{TestClass.class});
testExecutor.run();
}
}
So the problem comes because the TestClass.class is from another maven module and actually the resources which I want to get are loaded in that module classpath. In that TestClass I have the following method:
public Object[][] retrieveFile() throws IOException {
String[] issuesKeys = IOUtils.toString(
Thread.currentThread().getContextClassLoader().getResourceAsStream("fileName"))
.split("\\n");
....
....
}
If I build the module where the TestClass belongs to everything is working fine, because Thread.currentThread().getContextClassLoader() is loading the correct ClassLoader, but if run it with my plugin as I run the test programatically Thread.currentThread().getContextClassLoader() is loading the classpath of my plugin, so the file I want to retrive is not there and a RuntimeException is thrown.
So my question is how to get the correct ClassLoader so that to be able to get the file or is there a way to load files in classpath manually with java?
I found one solution- I modified the currentThread ClassLoader- I got all required classes from the classpath of the maven project I am executing the plugin on. This is possible with injecting the MavenProject bean:
#Component
private MavenProject project;
Trying to load spring beans from custom groovy file in Grails 2.3.7. I know this question has been asked before, but after hours of searching, I'm unable to find a consistent approach that loads from the classpath.
The Goal
Modularize resources.groovy into multiple custom resource files
Put custom resource files in standard location: grails-app/conf/spring
Use plugin to do the magic; minimize overhead
Tried...
//## grails-app/conf/spring/MyBeansConfig.groovy
beans {
testsvc(TestService){
msg = 'hello'
}
}
Note above, I'm using beans {}, not beans = {}, apparently it makes a difference:
resources.groovy
This works...
beans = {
loadBeans 'file:C:\\Proj\Test1\grails-app\\conf\\spring\\MyBeansConfig.groovy'
}
...and according to docs, this should too, but doesn't:
importBeans("classpath:*MyBeansConfig.groovy")
UPDATE - WORKING OPTIONS
(working for Grails 2.3.7)
Option 1: (src/java)
Following lukelazarovic advice, this approach works since Grails automatically copies (not compiles) groovy files in src/java to the classpath; works fine in eclipse and with war deployment:
//bean config files here
src/java/MyBeansConfig.groovy
src/java/FooBeansConfig.groovy
//resources.groovy
loadBeans('classpath*:*BeansConfig.groovy')
Options 2: (grails-app/conf/spring)
This approach allows for custom bean config files in grails-app/conf/spring (credits to ideascultor and mark.esher)
//bean config files here
grails-app/conf/spring/MyBeansConfig.groovy
//## resources.groovy
//for eclipse/local testing
loadBeans('file:./grails-app/conf/spring/*BeansConfig.groovy')
//for WAR/classpath
loadBeans('classpath*:*BeansConfig.groovy')
//## BuildConfig.groovy
grails.war.resources = { stagingDir, args ->
copy(todir: "${stagingDir}/WEB-INF/classes/spring") {
fileset(dir:"grails-app/conf/spring") {
include(name: "*BeansConfig.groovy")
exclude(name: "resources.groovy")
exclude(name: "resources.xml")
}
}
}
Options 3: (Custom Plugin)
If you're using custom plugins, this approach is ideal; boiler plate config gets refactored into the plugin:
Plugin Config
//## scripts/_Events.groovy
eventCreateWarStart = { warName, stagingDir ->
def libDir = new File("${stagingDir}/WEB-INF/classes/spring")
ant.copy(todir: libDir) {
fileset(dir:"grails-app/conf/spring") {
include(name: "*BeansConfig.groovy")
exclude(name: "resources.groovy")
exclude(name: "resources.xml")
}
}
}
//## MyGrailsPlugin.groovy
def doWithSpring = {
loadBeans('file:./grails-app/conf/spring/*BeansConfig.groovy')
loadBeans('classpath*:*BeansConfig.groovy')
}
Grails App
No config!...just create your bean config files using the *BeansConfig.groovy convention, good to go!
//bean config files here
grails-app/conf/spring/MyBeansConfig.groovy
Update 9/24/2015
Found some issues with option 3:
loading of duplicate resource files
spring resources not configured correctly for test-app
We managed to fix the above issue such that any resource files in grails-app/conf/spring work the same when executing Grails in eclipse, WAR, test-app, etc.
First step: we created a class to have more control over locating and loading resource files; this was necessary as some files were being loaded more than once due to cross-referenced beans.
We're using a plugin to encapsulate this functionality, so this class lives in that plugin:
class CustomResourceLoader {
static loadSpringBeans(BeanBuilder bb){
def files = ['*'] //load all resource files
//match resources using multiple methods
def matchedResourceList = []
files.each {
matchedResourceList +=
getPatternResolvedResources("file:./grails-app/conf/spring/" + it + ".groovy").toList()
matchedResourceList +=
getPathMatchedResources("classpath*:spring/" + it + ".groovy").toList()
}
//eliminate duplicates resource loaded from recursive reference
def resourceMap = [:]
matchedResourceList.each{
if(it) resourceMap.put(it.getFilename(), it)
}
//make resources.groovy first in list
def resourcesFile = resourceMap.remove("resources.groovy")
if(!resourcesFile)
throw new RuntimeException("resources.groovy was not found where expected!")
def resourcesToLoad = [resourcesFile]
resourceMap.each{k,v -> resourcesToLoad << v }
log.debug("Spring resource files about to load: ")
resourcesToLoad.each{ bb.loadBeans(it) }
}
static def getPatternResolvedResources(path){
ResourcePatternResolver resourcePatternResolver =
new PathMatchingResourcePatternResolver();
return resourcePatternResolver.getResources(path);
}
static def getPathMatchedResources(path){
return new PathMatchingResourcePatternResolver().getResources(path)
}
}
Removed BeansConfig.groovy suffix; WAR generation now picks up any resources in grails-app/conf/spring
plugin, _Events.groovy
eventCreateWarStart = { warName, stagingDir ->
def libDir = new File("${stagingDir}/WEB-INF/classes/spring")
ant.copy(todir: libDir) {
fileset(dir:"grails-app/conf/spring") {
include(name: "*.groovy")
exclude(name: "resources.xml")
}
}
}
}
In the plugin definition file, call the loader from doWithSpring(), passing BeanBuilder (the delegate) as the argument (very important):
def doWithSpring = {
CustomResourceLoader.loadSpringBeans(delegate)
}
That's it, there is no requirement on users of the plugin aside from creating custom resource files in grails-app/conf/spring
I had a similar problem just a few days ago, with a groovy configuration file that I added into grails-app/conf. While this works with other resources (they are copied and available on the classpath), the problem with the groovy file was simply that it was compiled and the class file was included, i.e. not the groovy file itself.
I didn't find any good documentation on how this should be done and finally just added it to web-app/WEB-INF/classes. Groovy files placed here will be copied (not compiled) and available on the classpath.
I had the same problem with custom XML files in Grails 2.1.2.
Having XML resources in grails-app/conf/spring didn't work in production environment AFAIR.
To make it working both in development and production environments I finally put the resources into src/java. I think you can achieve the same result by putting your groovy files into src/groovy.
We can import beans from different groovy/xml file in the following way too : -
use the following in resources.groovy -
importBeans 'file:camel-beans.groovy'
OR
importBeans('classpath:/camel-config.xml')
Place camel-beans.groovy along with resources.groovy and provide package as "package spring" for first case, otherwise put it in web app classpath and use the second way to do it.
If your resources.groovy is at following path
grails-app/conf/spring/resources.groovy
and your camel-beans.groovy is at following path
grails-app/conf/spring/camel-beans.groovy
then you can reference camel-beans.groovy in resources.groovy file by adding following line in resources.groovy
importBeans('file:**/camel-beans.groovy')
I am trying to write a custom rule for Maven enforcer plugin which is very similar to the RequireSameVersions rule that is included with the enforcer plugin.
I want to look at all dependencies of a project, and if they have a custom property set, then we must ensure that the property is the same across all dependencies (whilst ignoring any dependencies without that property set, as these are version agnostic).
The problem I am facing is that in the code for the RequireSameVersions rule, the artifacts have the version exposed in the API, such that you can call artifact.getVersion() for each dependent artifact, however the only Object that it seems you can call getProperties() on is the maven project itself.
Thus, the code I would really like to write for the custom rule is:
public void execute(EnforcerRuleHelper helper) throws EnforcerRuleException {
Set<String> versions = new HashSet<String>();
MavenProject project = null;
try {
project = (MavenProject) helper.evaluate("${project}");
Set<Artifact> dependentArtifacts = project.getDependencyArtifacts();
for (Artifact artifact : dependentArtifacts) {
Properties childProperties = getProperties(artifact); //TODO: what to put in this method
String versionNumber = childProperties.getProperty("bespoke.version");
if (versionNumber != null) {
versions.add(versionNumber);
}
}
} catch (ExpressionEvaluationException eee) {
throw new EnforcerRuleException(
"Unable to retrieve the MavenProject: ", eee);
}
if (versions.size() > 1) {
// we have more than one version type across child projects, so we
// fail the build.
throw new EnforcerRuleException(
"modules of this project refer to more than one version");
}
}
However, I don't know what to put in the getProperties(artifact) method. Any suggestions here would be fantastic, thank you.
I am trying to get gwt-test-utils to work. I set up the project in the following way:
src/main/java : all the java source code
src/test/java : the test source code
src/test/resources : resource files for the tests
I am building my project with gradle and eclipse. Gradle uses these directories correctly by default and I added all three of them as source directories to Eclipse.
I have successfully built and run the project and was able to execute some plain old JUnit tests as well as a GWTTestCase, so I think I set up the project and its dependencies correctly.
Now I wanted to use gwt-test-utils for some more advanced integration tests. To do so I did the following:
Add the gwt-test-utils and gwt-test-utils-csv to my dependencies
gwtTestUtilsVersion = '0.45'
testCompile group:'com.googlecode.gwt-test-utils', name:'gwt-test-utils', version:gwtTestUtilsVersion
testCompile group:'com.googlecode.gwt-test-utils', name:'gwt-test-utils-csv', version:gwtTestUtilsVersion
Add a gwt-test-utils.properties file to the directory src/test/resources/META-INF with the following content:
path/to/my/module = gwt-module
Added a class that extends GwtCsvTest to a package in the src/test/java directory. It is modeled after the second example in HowToWriteCsvScenario from the gwt-test-utils project wiki, replacing occurrence of their example classes with mine. It looks like this
#CsvDirectory(value = "gwtTests")
public class LoginLogoutTest extends GwtCsvTest
{
#Mock
private MainServiceAsync mainService;
private AppController appController = new AppController();
#CsvMethod
public void initApp()
{
appController.onModuleLoad();
}
#Before
public void setup()
{
GwtFinder.registerNodeFinder("myApp", new NodeObjectFinder()
{
#Override
public Object find(Node node)
{
return csvRunner.getNodeValue(appController, node);
}
});
GwtFinder.registerNodeFinder("loginView", new NodeObjectFinder()
{
#Override
public Object find(Node node)
{
return csvRunner.getNodeValue(appController.getRootPresenter().getCurrentlyActiveSubPresenters().iterator().next().getView(), node);
}
});
addGwtCreateHandler(createRemoteServiceCreateHandler());
}
}
added a csv-file for configuring the test to src/test/resources/gwtTests with the following content
start
initApp
assertExist;/loginView/emailTextBox
I tried executing it via the Eclipse's Run As > JUnit Test and indirectly via gradle build (which executes all the test cases, not just this one). Both lead to the same error:
ERROR GwtTreeLogger Unable to find type 'myPackage.client.AppController'
ERROR GwtTreeLogger Hint: Check that the type name 'myPackage.client.AppController' is really what you meant
ERROR GwtTreeLogger Hint: Check that your classpath includes all required source roots
The AppController class is the entry-point configured in the module I configured in gwt-test-utils.properties, which makes me think that configuration works correctly and the rest of the setup (dependencies and all) work as well.
In an earlier version I used the same file as a subclass of GWTTestCase and created an AppController instance in the same way. That worked, so I'm pretty sure the class path is setup correctly to include it as well. I also tried changing it back to the previous version just now and it still works.
I have no clue why the class is not found. Is there anything gwt-test-utils does differently which means I need to specifically set the class path for it? Otherwise it should just work, since both gradle and eclipse know about all the relevant source folders and dependencies.