Handle cross dependencies between update sites

Dear all,

which is the way to best handle the dependencies across update sites? What I would like is that when one plugin is installed it gets the missing dependencies. For some jars this is the case and they are automatically installed. For instance this dependency

<dependency>
            <groupId>sc.fiji</groupId>
            <artifactId>simplified-io</artifactId>
            <version>1.0.2</version>
        </dependency>

For others like

        <dependency>
            <groupId>fr.inra.ijpb</groupId>
            <artifactId>MorphoLibJ_</artifactId>
        </dependency>

Fiji does not seem to recognize the dependency. I have to manually check the IJPB-plugin update to get the respective jar.

When I run in the developer environment the jar are correctly handled by mvn and I can test the plugin. What should I do? Should I provide the other jars within my update site? Will this not cause conflicts?

Here is the full pom.xml file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.scijava</groupId>
        <artifactId>pom-scijava</artifactId>
        <version>29.2.1</version>
        <relativePath />
    </parent>

    <groupId>de.mpibpc.liveim</groupId>
    <artifactId>liveim-nucleipolarity</artifactId>
    <version>0.2.11-SNAPSHOT</version>

    <name>Fiji plugin to draw spheres using a sphere coordinates from csv file (BoneJ)</name>
    <description>Plugin extracts coordinates from the csv file that contains center coordinates and radius of 2 spheres and the name of an associated image.
        From this a labeled image, a distance map, and labeled sections are created</description>
    <url>https://gitlab.gwdg.de/schuh-meiosis/cavazza_etal_cell_2021_parental_genome_unification</url>
    <inceptionYear>2019</inceptionYear>
    <organization>
        <name>MPI-BPC  Live-cell Imaging Facility</name>
        <url>https://www.mpibpc.mpg.de/en</url>
    </organization>
    <licenses>
        <license>
            <name>Simplified BSD License</name>
            <distribution>repo</distribution>
        </license>
    </licenses>

    <developers>
        <developer>
            <name>Antonio Politi</name>
            <url>http://imagej.net/User:Liveim</url>
            <roles>
                <role>lead</role>
                <role>developer</role>
                <role>debugger</role>
                <role>reviewer</role>
                <role>support</role>
                <role>maintainer</role>
            </roles>
        </developer>
    </developers>

    <contributors>
        <contributor><name>None</name></contributor>
    </contributors>
    <mailingLists>
        <mailingList><name>None</name></mailingList>
    </mailingLists>
    <scm>
        <connection>scm:git:git://gitlab.gwdg.de/schuh-meiosis/cavazza_etal_cell_2021_parental_genome_unification</connection>
    </scm>
    <issueManagement>
        <system>GitLab</system>
    </issueManagement>
    <ciManagement>
        <system>None</system>
    </ciManagement>
    <properties>
        <package-name>de.mpibpc.liveim.nucleipolarity</package-name>
        <license.licenseName>bsd_2</license.licenseName>
        <license.copyrightOwners>MPIBPC</license.copyrightOwners>
        <license.projectName>Fiji distribution of ImageJ for the life sciences.</license.projectName>
    </properties>
    <repositories>
        <repository>
            <id>scijava.public</id>
            <url>https://maven.scijava.org/content/groups/public</url>
        </repository>

    </repositories>
    <dependencies>
        <dependency>
            <groupId>net.imagej</groupId>
            <artifactId>imagej</artifactId>
        </dependency>



        <dependency>
            <groupId>net.imagej</groupId>
            <artifactId>imagej-legacy</artifactId>
        </dependency>

        <dependency>
            <groupId>io.scif</groupId>
            <artifactId>scifio</artifactId>
        </dependency>

        <dependency>
            <groupId>fr.inra.ijpb</groupId>
            <artifactId>MorphoLibJ_</artifactId>
        </dependency>


        <dependency>
            <groupId>com.opencsv</groupId>
            <artifactId>opencsv</artifactId>
            <version>3.9</version>
        </dependency>

        <dependency>
            <groupId>sc.fiji</groupId>
            <artifactId>simplified-io</artifactId>
            <version>1.0.2</version>
        </dependency>
    </dependencies>
</project>

Thanks

Antonio

Hello,

I think there are two ways to solve this problem.

  1. Upload the required library with your jar. The update site recognizes if it is already installed or not when the respective update site is enabled. I guess it also sees conflicts.
  2. At startup of the plugin check if the necessary dependencies are installed. Ask then the user to enable the respective update site.

I found none of the solutions optimal, but could not think at anything else.

Are there (e.g. @Christian_Tischer, @haesleinhuepf, @ctrueden) other opinions about this ?

Thanks

Antonio

Is there a guideline for this?

1 Like

Hey @apoliti ,

I think you’re right and I typically follow strategy 2. Dependencies which have an update site or are core of ImageJ, I wouldn’t upload to my site to prevent clashes when those are updated elsewhere.
Furthermore, users may find detailed installation instructions helpful to prevent issues.

Cheers,
Robert

Thanks @haesleinhuepf,

what would you do in the java code to check if the package exists and then issue a message to the user?
Something like this in before the import call

try {
	Class.forName("package_needed");
} catch( Exception e)
{ IJ.log("Enable the update site package")
return}

Thanks

Antonio

1 Like

Hi @apoliti ,

I tried similar things and couldn’t make it to work. Thus, I’m checking for the existence of jar-files. I remember @LThomas posted some code not to long ago that checks for existing classes, but I can’t find it anymore :slight_smile:

If you get it to work, let us know how.

Cheers,
Robert

If your dependency comes with a specific update site, you can check that this update site is active and if not, show a message asking the user to activate it.

#@ UpdateService updateService
println(updateService.getUpdateSite("Java-8").isActive())
println(updateService.getUpdateSite("Fiji-Legacy").isActive())

However I dont know how you get the UpdateService in Java, I was using that for scripting, but I guess there is a way.

See original post from Curtis

1 Like

Yes it does not work :frowning:
The import command has always priority so that anything that tries to verify the existence of a class will be executed after the import. This is very annoying indeed.

When is the checkInstallation performed?
When i enable only clij2 I get class not found errors but no warning that I should enable also clij.

I tried with clij2 and it does not warn me that I miss clij for running some of the functions.

Provided you have a SciJava Context:

UpdateService updateService = context.service(UpdateService.class);

I know that @ehrenfeu also checks for the existence of the Action Bar plugin before activating their IMCF Toolbar. The check is done in Javascript though…

2 Likes

Hello @LThomas,
I am trying a few things now. It seems that the import command has priority above anything else, it is checked at startup.

I guess that before running the proper script/plugin one has to execute an additional script that just checks for the updatesite.

2 Likes

Just as a note: If you just invoke a plugin with IJ.run("…") then you do not have issues with the imports.

However, sometime it is quite handy to call directly the class and for the moment I do not see a good solution.

Did you try this:

It looks like forName(String name, boolean initialize, ClassLoader loader) could do the job, but I didn’t test it myself.

It all works if you have no import command. The import causes a sort of “compilation” error.

Having said that, the command does throw an error if you have not the package

Class.forName("imagescience.image.Image", false, this.getClass().getClassLoader())

I would have thought that you leave away the import statement in this scenario, and rely entirely on Class.forName().newInstance() to do whatever you need to do.

Did I misunderstand your use case?

Thanks Jan!
I did not know you can instantiate a class just like this!
If you use Class.forName().newInstance() you do not need the import command.

The only drawback is that you lose the IDE completion as it does not know what you are talking about.

Here to summarize the discussion a script that works for me. Thanks to all for the helpful contributions.

#@ UpdateService updateService
// Correct syntax is #@ and not // #@!
// Example groovy script how to avoid import and check if a class exists
// This allows to check if additional update sites should be anabled or not
// Author: Antonio Politi, May 2021, MPIBPC

import ij.IJ

//if (!checkClass()) 
//    return;

if (!checkClassUpdatesite(updateService)) 
    return;


imp = IJ.openImage("http://imagej.nih.gov/ij/images/Dot_Blot.jpg");
imp.show()

// equivalent to import imagescience.feature.Laplacian as Laplacian
// lap = new Laplacian()
lap = Class.forName("imagescience.feature.Laplacian").newInstance()

// Equivalent to import imagescience.image.Image as Image and usage of static method wrap()
Image = Class.forName("imagescience.image.Image")

imp_laplace = lap.run(Image.wrap(imp), 1);
imp_laplace = imp_laplace.imageplus();

imp_laplace.show()

def checkClass() {
    try {
       	Class.forName("imagescience.feature.Image");
        return true;
    } catch (Exception e) {
        IJ.showMessage("Please enable the ImageScience update site! [Help > Update > Manage update site > [x] ImageScience] ")
        return false;
    }
}

def checkClassUpdatesite(updateService) {
	if (!updateService.getUpdateSite("ImageScience").isActive()){
		IJ.showMessage("Please enable the ImageScience update site! [Help > Update > Manage update site > [x] ImageScience] ")
		return false
	} else {
		return true
	}

}
1 Like

Great, thanks for sharing the final script, @apoliti!

Please note that this is actually incorrect parameter syntax. The two options to declare script parameters are:

  • the comment-based syntax (deprecated), which is only valid in the header of the script:
    // @UpdateService updateService
    
  • the (language-agnostic) #@ syntax (recommended), valid anywhere in the script, e.g. also after a license header:
    #@ UpdateService updateService
    

That it works in your script is actually a bug that caused inconsistent behavior (commented-out parameters being parsed in the header, but not further down in the script), that was fixed here:

Hello Jan,

good point!

Using

#@ UpdateService updateService

in an IDE (I used IntelliJ) you get a lot of warnings or errors if you directly call it. This was the reason to comment it. It is not required in the macro language.

I fixed the script in this sense.

1 Like

As a command, here is what I would suggest (untested):

import net.imagej.updater.UpdateService;
import net.imagej.updater.UpdateSite;
import org.scijava.plugin.Parameter;
import org.scijava.ui.UIService;
...
public class MyCommand implements Command {
  ...
  @Parameter
  private UpdateService updateService;

  @Parameter
  private UIService uiService;

  @Override
  public void run() {
    UpdateSite site = updateService.getUpdateSite("MyUpdateSite");
    if (site == null || !site.isEnabled()) {
      uiService.showDialog("Please enable the MyUpdateSite update site before running this command.");
      return;
    }
    ...
  }
}

Or if you are working with a SciJava Context:

UpdateSite site = context.service(UpdateService.class).getUpdateSite("RequiredUpdateSite");
if (site == null || !site.isActive()) dieInAFire();

Or with an ImageJ gateway:

UpdateSite site = ij.get(UpdateService.class).getUpdateSite("RequiredUpdateSite");
if (site == null || !site.isActive()) cryLotsOfTears();
2 Likes