Add dependence of the project into my custom plugin

plugins
(Vincenzo Palazzo) #1

Hi guys,

I’m new into the Gradle world and I want developer my custom plugin for Gradle but I have a problem.

My plugin must communicate with the Database(create database, drop database), so I have need for a JDBC driver, this driver is must into the project that utilizes my plugin.

My question is: How getting a dependency jar for database into my plugin?

I look this post but I did not find this object dependencyNamesSet.

The my code of the my task is this

package io.vincentpalazzo.gradledatabase.task;

import io.vincentpalazzo.gradledatabase.exstension.GradleDatabaseExstension;
import io.vincentpalazzo.gradledatabase.persistence.DataSurce;
import org.gradle.api.DefaultTask;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.tasks.TaskAction;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Iterator;
import java.io.File;
import java.util.Set;

/**
 * @author https://github.com/vincenzopalazzo
 */
public class CreateDatabaseTask extends DefaultTask {

    @TaskAction
    public void createAction() {

        GradleDatabaseExstension project = getProject().getExtensions().findByType(GradleDatabaseExstension.class);

        String url = project.getUrl();
        String driverClass = project.getDriver(); //The drive name database is different
        String username = project.getUsername();
        String password = project.getPassword();
        String nameDatabase = project.getNameDatabase();
        String nameJar = project.getNameJar();

        if (findDependecyFileJarForDriver(nameJar)) {
            System.out.println("Jar findend");
        } else {
            System.out.println("Jar not found");
        }

        DataSurce dataSource = new DataSurce();
        if (dataSource.connectionDatabase(driverClass, url, username, password)) {
            if (dataSource.createDatabese(nameDatabase)) {
                System.out.println("Database " + nameDatabase + " created");
            }
        }
    }

    private boolean findDependecyFileJarForDriver(String nameJar) {
        if (nameJar == null || nameJar.isEmpty()) {
            throw new IllegalArgumentException("The input parameter is null");
        }

        Iterator<Configuration> iterable = getProject().getConfigurations().iterator();
        boolean finded = false;

        while ((!finded) || (iterable.hasNext())) {
            Configuration configuration = iterable.next();
            Set<File> filesSet = configuration.resolve();
            for (File file : filesSet) {
                String nameFile = file.getName();
                if (nameFile.contains(nameJar)) {
                    //Now?;
                    finded = true;
                }
            }
        }
        return finded;
    }
}

My Code for the DataSurce is this

package io.vincentpalazzo.gradledatabase.persistence;

import java.sql.*;

/**
 * @author https://github.com/vincenzopalazzo
 */
public class DataSurce {

    private Connection connection;

    public Connection getConnection() {
        return connection;
    }

    public boolean connectionDatabase(String driverPackage, String url, String username, String password){
        if((url == null || url.isEmpty()) ||
                (username == null || username.isEmpty()) ||
                (password == null || password.isEmpty())){
            //TODO create a exeption more specific
            throw new IllegalArgumentException("Parameter function not valid");
        }

        registerDriver(driverPackage);

        try {
            connection = DriverManager.getConnection(url, username, password);
            return true;
        } catch (SQLException e) {
            //TODO create a exeption more specific
            throw new IllegalArgumentException("Exception generated is: " + e.getLocalizedMessage());
        }
    }

    public boolean createDatabese(String nameDatabase){
        if(nameDatabase == null || nameDatabase.isEmpty()){
            //TODO create a exeption more specific
            throw new IllegalArgumentException("Parameter function not valid");
        }

        String query = "CREATE DATABASE " + nameDatabase;

        PreparedStatement preparedStatement = null;
        try {
            preparedStatement = connection.prepareStatement(query);
            preparedStatement.executeUpdate();
            connection.close();
            return true;
        } catch (SQLException e) {
            throw new IllegalArgumentException("Exception generate is: " + e.getLocalizedMessage());
        }
    }

    public boolean deleteDatabese(String nameDatabase){
        if(nameDatabase == null || nameDatabase.isEmpty()){
            //TODO create a exeption more specific
            throw new IllegalArgumentException("Parameter function not valid");
        }

        String query = "DROP DATABASE " + nameDatabase;

        PreparedStatement preparedStatement = null;
        try {
            preparedStatement = connection.prepareStatement(query);
            preparedStatement.executeUpdate();
            connection.close();
            return true;
        } catch (SQLException e) {
            throw new IllegalArgumentException("Exception generate is: " + e.getLocalizedMessage());
        }
    }

    private boolean registerDriver(String driverPackage) {
        if(driverPackage == null || driverPackage.isEmpty()){
            //TODO create a exeption more specific
            throw new IllegalArgumentException("Parameter function not valid");
        }
        Driver driver = null;

        try {
           driver = (Driver) ClassLoader.getSystemClassLoader().loadClass(driverPackage).newInstance();
        } catch (InstantiationException e) {
            //TODO create a exeption more specific
            throw new IllegalArgumentException("Exception generated is: " + e.getLocalizedMessage());
        } catch (IllegalAccessException e) {
            //TODO create a exeption more specific
            throw new IllegalArgumentException("Exception generated is: " + e.getLocalizedMessage());
        } catch (ClassNotFoundException e) {
            //TODO create a exeption more specific
            throw new IllegalArgumentException("Exception generated is: " + e.getLocalizedMessage());
        }

        try {
            DriverManager.registerDriver(driver);
            return true;
        } catch (SQLException e) {
            //TODO create a exeption more specific
            throw new IllegalArgumentException("Exception generated is: " + e.getLocalizedMessage());
        }
    }
}
Error Resolving configuration 'apiElements' directly is not allowed
(LingoCoder) #2

How are things you coming along with your plugin? I might be able to help if you still need it.

I uploaded this project that demonstrates how you could use a plugin of mine to solve your plugin’s dependency access problem.

There’s a full README in the download. But I’ll give you the TL;DR here:

The download contains a bare-bones simulation of your gradle-database plugin. There’s an example of a build script that a user of your gradle-database plugin would run. Essentially, my plugin gives your plugin access to your users’ database driver Jar. Users of your plugin will need to do the following in their build scripts:

  1. Configure a dependencies block that includes their required database driver artifact.
  2. Using my plugin, fetch the user’s database driver artifact from Gradle’s dependency cache.
  3. Set the fetched database driver artifact as the value of the appropriate property of your plugin’s task.

With the appropriate property set on your task, your plugin code will therefore have access to the database driver Jar file that my plugin fetched from Gradle’s dependency cache and passed to your task from the build script. In your plugin code, you would use the Jar file however you intend to.

The stripped-down database plugin and build script contained in the download, is a basic working example of the above use case.

Get in touch if I can answer any questions for you.

(Vincenzo Palazzo) #3

Hi

My project now work and I find the jar but I think that with Gradle is possible to increment the quality for the plugin.
For the moment I resolved the problem with this code

 Set<File> classpath = getProject().getConfigurations().getByName("compileClasspath").getFiles();
        for(File file : classpath){
            if(file.getName().contains(nameJar)){
                System.out.println("Contains dependende " + file.getName());
                try {
                    URLClassLoader classLoader = new URLClassLoader(new URL[]{file.toURI().toURL()});
                    this.classLoaderJar = classLoader;
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                }
            }
        }

Some question on your plugin, if I understand the function of the your plugin finds the dependence, an example if I want to find the dependencies on some directory your plugin finde the jar, right?

I’m new in the Gradle world and I’m learning this technology for creating my plugins, an example if I want work on the Tomcat server with the version that downloads into tomcat site and not with the Ide version and I will developer my plugin for using a task ant by tomcat api with gradle and if I understood the function your plugin, I think that your plugin interesting.

Sorry for my terrible English but I’m learning

(LingoCoder) #4

Thanks for getting back to me.

Sorry. No. It looks for Jars only in two places:

  1. Maven local (typically %HOME/.m2/repository)
  2. Gradle’s dependency cache ($GRADLE_USER_HOME/caches)

The user is assumed to have explicitly declared an artifact in a dependency{} block. If they did, and if Gradle successfully resolves the artifact, then the artifact should pretty much always be in one of the above two places for my plugin to find.

Making it configurable to search in a user-specified location, might not be a bad idea though for a future version. Thanks for the tip :+1:

(Vincenzo Palazzo) #5

The your plugin si very util, if you add the dependence for load jar in another dir is fantastic, an example if I add the my other project and not have creste last versione because is only test with the personal directory your project is very util :blush:

(LingoCoder) #6

Thanks again :blush: I intend to give your suggestion some serious consideration.

Meanwhile, download the demo project if you ever have any spare time. Try to think of ways to break the Jar plugin. It needs some real world usage to help it improve its utility.

(Vincenzo Palazzo) #7

Yes I already have dowload your project example and in the next weeks I update the my project with your plugin because it help me with improve the quality plugin. Thanks so much

1 Like
(LingoCoder) #8

Hey! That would be awesome. Feel free to get in touch if you eventually think of any questions you want to ask about the JarExec plugin.

I thought some more about the Tomcat/IDE/personal directory scenario you described earlier. I might not fully understand your use case. But even in the current version of the JarExec, you could do something like this:

....
def dbJar = jarhelper.fetch('non.existent:artifact-gav:0.0.0').orElse(file('/full/path/to/tomcat/version.jar'))
...

…where 'non.existent:artifact-gav:0.0.0' are fake coordinates of a dummy artifact that you know for sure does not exist. The non-existent artifact will, of course, never be found.

In that case, the helper will instead set dbJar to be the user-specified file() in the orElse() call. In other words, if the helper fails to fetch your first option, it will return a file known to exist at a known location. The default option.

Probably not the precise use case you described earlier. But it might come in handy in other circumstances.

(Vincenzo Palazzo) #9

Oh this is a good news for me.
In the my precendent use case, tomcat has a the personal jars, an exampe jasper.jar and for precompile page jsp into my project I have need for the jar for jasper, now is possible two option

  • Into plugin adding dependence and non included it when i create the war
  • The plugin usinging a jar into tomcat_dir/lib for compile the jsp page, in this case I didn’t have need for exluded the dependence when I create a war
(Daniel G) #10

I not sure why you are messing around with looking up a jar in foreign configurations.

I’ve seen and used lots of usecases, where I was in need of a specific classpath (just imagine the database driver needs of a set of jars to work). Therefor you can easily create your own configuration and let the user define a dependency in it.

Then you won’t lookup a specifically named jar file, but simply resolve the configuration and use it as classpath. Your plugin may create a specific configuration by itself but it’s also totaly fine to let the user do smth like this:

configurations {
    databaseDriver
}

dependencies {
    databaseDriver "org.postgresql:postgresql:42.2.5"
}

settingDatabase {
    ....
    // instead of : nameJar 
    driverConfiguration = configurations.databaseDriver
}

This way a user has to define it’s own configuration, but also has the control on how to name it. The main advantage is to not mix up dependencies and task configuration. Anyone sees clearly what classpath your plugin will use and not forget to update the nameJar property if he updates the dependency. One may even declare a filesystem based repository to resolve only locally available drivers.

repositories {
   flatDir {
       dirs 'libs'
   }
}

Here is also a official example on how to use this pattern with custom ant tasks, which is not that different to your plugin:
https://docs.gradle.org/current/userguide/managing_dependency_configurations.html#defining_custom_configurations

(Vincenzo Palazzo) #11

Hi,
Thanks for your answer and I have some question for you, with your method I find the dependencies databaseDriver into my task with this code right?

Set<File> driver = getProject().getConfigurations().getByName("databaseDriver").getFiles();

(Daniel G) #12

Yeah that would be it if you directly lookup the configuration. While that’s possible you might also wanne create exactly this configuration in your code too, otherwise you depend on a fixed configuration name that might not exist.

The other way shown in my example is to give the configuration (or maybe just the name) as a configureable property to your plugin. This way someone may use your task for different databases with different drivers in one build.gradle using specific configurations for every database.

(Vincenzo Palazzo) #13

I was do a simil solutions but it not work, I have an error when I went create a driver class,

This is my solution look the method findDependecyFileJarForDriver, The your solution is different of the my?

(Daniel G) #14

I’m sorry, but I don’t get what you mean.
Sure the solution I suggested is a little different. After all you should not use/need the method findDependecyFileJarForDriver anyomore but put the whole collection of files in the Classloaders path.

Just some minor hint for your plugin. The Extension you wrote seems to be used very different from what the docs suggest. In https://guides.gradle.org/implementing-gradle-plugins/#capturing_user_input_to_configure_plugin_runtime_behavior the extension is registered and while registering the task a Action is also provided to the gradle API to apply the extensions values to the task. In your plugin the tasks request the projects extension while they are beeing processed.
While it seems to be a minor difference, it will prevent you from reusing your tasks.

(Vincenzo Palazzo) #15

Now I don’t understand but I going to read your reference and if have some questions I will write to you.
Thanks for your advise for improving my plugin

You Sorry me, my English is terrible, sorry