14

I'm building a Java desktop application, using JavaFX, Gradle, javafx-gradle-plugin. This application connects to a server that I also build. When I compile a release version, running gradle jfxNative, I want it to talk to the production server; but otherwise, I want it to talk to localhost.

What's the proper Java/Gradle way of handling this? Some sort of compilation profile?

Pablo Fernandez
  • 279,434
  • 135
  • 377
  • 622
  • Please check out this [answer](https://stackoverflow.com/questions/29094656/maven-or-gradle-build-types-variants) – dev.bmax Sep 19 '17 at 14:57
  • Do you have an example? How do you start it both for development and production? With different tasks? – Opal Sep 19 '17 at 19:26
  • @Opal: It's a desktop application, for production people install it and then double click the icon (or whatever). For development I normally run it from IntelliJ or with gradle run. – Pablo Fernandez Sep 19 '17 at 19:47
  • Thanks! How do you then distinguish if this is production version or not? How do you handler server URL now? – Opal Sep 19 '17 at 20:06
  • @Opal: well, I don't, this is the question. How to do it. Right now, while I'm developing, the app is hardcoded to connect to localhost. Ideally, when I build the app for distribution (gradle jfxNative), then I want it to default to the production server. – Pablo Fernandez Sep 19 '17 at 20:13
  • That's why I use springboot for desktop app too :) – Maksym Sep 23 '17 at 15:56

6 Answers6

6

You can use Gradle's source sets for this:

Sample build.gradle:

apply plugin: 'java'

sourceSets {
    prod {
        java {
            srcDirs = ['src/main/java', 'src/prod/java']
        }
    }
    dev {
        java {
            srcDirs = ['src/main/java', 'src/dev/java']
        }
    }
}

task devJar(type: Jar) {
    from sourceSets.dev.output
    manifest {
        attributes("Main-Class": "MyPackage.MyClass")
    }
}

task prodJar(type: Jar) {
    from sourceSets.prod.output
    manifest {
        attributes("Main-Class": "MyPackage.MyClass")
    }
}

Now you can create two configuration classes for your dev and prod versions: src/dev/java/MyPackage/Configuration.java
src/prod/java/MyPackage/Configuration.java

All the common code will be in the main source set:
src/main/java/MyPackage/MyClass.java

MyClass can get some values from the configuration class (e.g. Configuration.getBaseUrl())

Running gradle devJar/ gradle prodJar builds one of the variants.

Note: you may need to extend jfxNative/jfxJar instead of Jar in your case.

dev.bmax
  • 8,998
  • 3
  • 30
  • 41
  • I think this is a configuration issue: "I want [prod] to talk to the production server; but otherwise, I want it to talk to localhost", the code should not need to differ between test and prod (in fact, it is better if it does not differ, as your testing is more realistic that way) – Rich Sep 25 '17 at 12:12
  • @Rich Source sets work equally well with resource files (see the link at the top of my answer) if you prefer to store the configuration in a text file. – dev.bmax Sep 25 '17 at 14:02
  • Including a different resource file in the JAR at build-time is an excellent solution to the problem, I agree. My comment refers to your answer as-written, which currently appears to suggest putting in different Java code: "src/prod/java". – Rich Sep 25 '17 at 15:07
4

The simplest solution: Have a configuration file containing such information. You either compile it into the application as a java resource or place it next to the jar file so it can be easily looked up via the filesystem.

With gradle all you need to do is define two build tasks with different input properties and insert the values into your properties file with groovy templating.

application.properties in src/main/resources:

server.address=${serverAddress}

add to your build.gradle

task setProductionServerAddress {
    processResources.expand([serverAddress: "https://app.example.com/v1"])
}
jfxJar.dependsOn(setProductionServerAddress)
jfxNative.dependsOn(setProductionServerAddress)

And then on the application:

Properties properties = new Properties();
properties.load(getClass().getResourceAsStream("/application.properties"));
if (properties.getProperty("server.address").equals("${serverAddress}")) {
  setUrl("http://localhost:8080/v1");
} else {
  setUrl(properties.getProperty("server.address"));
}
Pablo Fernandez
  • 279,434
  • 135
  • 377
  • 622
highstakes
  • 1,499
  • 9
  • 14
  • This is promising, but the building of a final JavaFX program happens with jfxNative, a task already provided. I tried adding the processResources to a task that jfxNative depended on, but it didn't work (not sure why yet). – Pablo Fernandez Sep 27 '17 at 11:12
1

Have it check environment variables for names of configuration files. Nothing to do with gradle or build. The same program should run properly wherever it is deployed.

See, e.g., Properties for dev and production

The easiest approach is to define a system property which specifies where the file system location for your data is. The production appserver would define one value (using java -D in the startup script), and your dev app server would define another value. Your application source would consult the system property value (using System.getProperty()) to discover the appropriate location

Also, this makes sense.

Put the information you need in JNDI - that's what it is designed for. Consider letting your application refuse to do anything if the information is not there.

Another reference: What is the best way to manage configuration data

EDIT: Well, what you're asking is logically not possible then, it seems to me. "It should connect to production, unless a specific someone wants to connect to development, but that feature should only be available to unknown persons" The start menu is only a shortcut for running the application, so you can install a "dev" shortcut with command line settings that are read as environment vars.

K.Nicholas
  • 10,956
  • 4
  • 46
  • 66
  • It's not deployed. It's a desktop application, so, environment variables do work, but it's annoying. You have to remember to manually set them when running the app, so, it's very error prone. – Pablo Fernandez Sep 19 '17 at 14:32
  • @Pablo what if you set the localhost as default environment when no arguments are provided (to avoid accidentally communicating with production environment)? – Serhiy Sep 19 '17 at 14:36
  • You can set in the system or use a batch file to set them first then run the app. – K.Nicholas Sep 19 '17 at 14:36
  • @Serhiy deploying what to prod? – Pablo Fernandez Sep 19 '17 at 14:37
  • @KarlNicholas if I set it in the system, then, the problem is reversed and I need to remember to undo them to connect to prod. I can't even run my app because it would be connecting to the wrong server. – Pablo Fernandez Sep 19 '17 at 14:38
  • Different batch files then, or a command line argument, or have the application ask when it runs. There is no silver bullet that I know of. – K.Nicholas Sep 19 '17 at 14:41
  • @KarlNicholas: when the app is installed, there's no batch file, it's a start menu shortcut, so, also there are no command line arguments. It shouldn't ask as this is only for developers. When I build a release version, there should be no trace of this (same as debugging symbols for example). – Pablo Fernandez Sep 19 '17 at 14:43
  • Well, what you're asking is logically not possible then, it seems to me. "It should connect to production, unless a specific someone wants to connect to development, but that feature should only be available to unknown persons" The start menu is only a shortcut for running the application, so you can install a "dev" shortcut with command line settings that are environment vars. – K.Nicholas Sep 19 '17 at 14:47
0

I would go with the one of the "12 factor app" concept which can be read here

One of its main concept is to use system environment variable which should determine whether you are working on a prod or dev or qa env etc. each project/environment/machine should contain its relevant env property, which after then can be retrieved through the gradle process similar to maven profile plugin.

An example for how to detect:

`if (project.hasProperty('env') && project.getProperty('env') == 'prod') {
    apply from: 'gradle/production.gradle'
} else {
    apply from: 'gradle/development.gradle'
}`

more on this approach using gradle can be found: gradle profile

Mickey Hovel
  • 982
  • 1
  • 15
  • 31
  • What is this project object? I'm familiar with 12 factor and I use it for my server applications. – Pablo Fernandez Sep 25 '17 at 13:33
  • a project is one of the gradle's objects. when building a gradle from the command line you can pass `-Penv=prod` and this will make gradle to pass the if condition above. more on gradle project [here](https://docs.gradle.org/current/userguide/writing_build_scripts.html#sec:project_api) – Mickey Hovel Sep 25 '17 at 14:23
0

You need to pick a configuration scheme (if JavaFX doesn't pick one for you).

I like https://github.com/typesafehub/config .

The config library will have instructions on how to make your "production" config differ from your "development" config.

See also JavaFX:Editable Configuration Files After Packaging

What's the proper Java/Gradle way of handling this? Some sort of compilation profile?

No, I would strongly recommend against compiling different code for production v.s. test. It will invalidate your testing. This should be handled in configuration, not in code. The conventional Java way of doing this is with configuration files (which can be compiled into the JAR as resources).

How to do this with Typesafe Config

I've had a bit of a look, and I am surprised not to find a good quality tutorial I can link you to here, sorry (I found a few rubbish ones). Perhaps this question will become a reference for others.

I would do something like this:

My guess as to why there aren't many tutorials for this is that all application or web Frameworks handle this for you.

Rich
  • 15,048
  • 2
  • 66
  • 119
  • Still, how do I make the production finished binary connect to one URL and the development one to a different one. – Pablo Fernandez Sep 25 '17 at 13:33
  • This solution once again reverts to using system properties / environment variables. Gradle is sufficient for making different prod/dev/test builds without them. – dev.bmax Sep 25 '17 at 14:48
  • I agree with you, @dev.bmax, that system properties / environment variables are undesirable here. I have updated my answer to show what I had in mind, although I don't have time to write a working POC. I agree that compiling in a different resource file, as you have suggested, is a good solution. – Rich Sep 25 '17 at 15:06
0

In my opinion and like others have suggested, this has little to do with the build and more to do with Run Time.

Therefore you could resort to checking for some kind of run time flag - a convenient and often used approach is to use System Properties.

On your Dev box, you could set an Environment variable - lets say FX _DESKTOP_APP_ENV = DEV or some such.

From your code you can look this up and decide the URL you want to use.

String env = System.getenv("FX _DESKTOP_APP_ENV");
String url = env == null ? "Production" : env;

On windows systems you can set up your system environment variables like so -- enter link description here

On *nix systems enter link description here

Hope this helps

jayaram S
  • 580
  • 6
  • 13