I made a program in Java that uses two external databases. The paths to these databases are hard-coded inside my program code.
In order to make this program usable for other users on other computers (who should also install these two databases on their computers), I think that the path for these two databases should be added as environmental variables ? How could this be done ?
I am not a professional when it comes to environmental variables, so can you please advise what should be done in this case?
Thanks in advance
To get the value of an environment variable in Java, you write something like this:
String pathToDatabase = System.getenv().get("PATH_TO_DATABASE");
(where PATH_TO_DATABASE is the name of the environment variable). This uses System.getenv() to get a map of all environment variables.
To set the value of an environment variable in Linux, your users can write something like this:
export PATH_TO_DATABASE=/this/is/the/path/to/the/database
before running the program.
Environment vars are usually not be the best way to handle app config, but if you must, the specific OS docs are needed to learn how to set them and from Java use:
Map map = System.getenv();
Rather than environment variables, a properties file would be useful and more portable. For example, in your properties file you could have the following:
db.url = jdbc://foo/bar?whatever
db.user = username
db.password = password
Then your code could read that in using the follow:
Properties properties = new Properties();
try {
properties.load(new FileInputStream("path/filename"));
} catch (IOException e) {
System.err.println( "Eeeek!" );
}
System.out.println( properties.getProperty( "db.url" ) );
Handily, properties objects allow you to specify defaults, so you could still have the hardcoded values if you want and then override them with the external file.
Related
Is there an existing Java solution to resolve file paths containing environment variables?
I need to convert this (simplified) method to handle the case where the provided path contains an environment variable (like %PROGRAMFILES(X86)%\SomeProgram\SomeFile.ini).
I know how to write code to extract the variable names, fetch their values and substitute them in the provided string but I can't help but feel I'm reinventing the wheel...
boolean isValidPath(String pathToValidate) {
File f = new File(pathToValidate);
if(f.exists()) {
return true;
}
return false;
}
Almost sure it does not exist in standard library simply because any environment variable is very specific in its usage. Some allow lists delimited by semicolons like PATH, some - only one single value. Also relying on values of env vars means that code becomes more platform-specific, and jre seems to avoid it at any reasonable cost.
There are already a couple of implementations of such substitutors on github, for example https://github.com/Haufe-Lexware/java-env-replacer.
You can actually get the environment variables details by calling System.getenv() and then you can write some logic (sample shown below) to achieve what you wanted:
public boolean isValidPath(String pathToValidate) {
String envValue = "";
for (String envName : env.keySet()) {
if(pathToValidate.contains(envName)) {
envValue= env.get(envName);
break;
}
}
//replace the envValue in pathToValidate
//Then check file exists or not & return true/false
}
I'm working at a company having a (too) complex configuration management process:
In each module there is an application.properties file. There are properties for the developers like: database.host = localhost
Properties which change in other environments are maintained in an application.properties file in an override-properties folder (for each module) like: database.host=#dbhost#
There is a default-deployment.properties file with default values for other environments like: database.HOST=noValueConfigured.DB_HOST
A postconfigure.properties file with DATABASE_ HOST=#configure.DB_HOST#
Those files are only needed if a property value depends on the environments (is different for development, testing, live).
Finally there is a Excel document with a sheet for every environment and a row like: configure.DB_HOST - a comment ... - 127.0.0.1 (just as example). The Excel is responsible for generating the correct property files for the rpm packages.
This process is not only complex but also error prone.
How could it be simplified/improved?
The approach should be compatbiel with Spring DI.
I would start with a master configuration file and generate the properties files to start with.
Ultimately you could have a set of proprties files which can be deployed in all environments e.g.
database.host = localhost
database.host.prod = proddb1
database.host.uat = uatdb1
i.e. use the environment/host/region/service at the end as a search path. This has the advantage that you can see the variations between environments.
You can implement this collect like this
public class SearchProperties extends Properties {
private final List<String> searchList;
public SearchProperties(List<String> searchList) {
this.searchList = searchList;
}
#Override
public String getProperty(String key) {
for (String s : searchList) {
String property = super.getProperty(key + "." + s);
if (property != null)
return property;
}
return super.getProperty(key);
}
You might construct this like
Properties prop = new SearchProperties(Arrays.asList(serverName, environment));
This way, if there is a match for that server, it will override, the environment which will overidden the default.
In Java 8 you can do
public String getProperty(String key) {
return searchList.stream()
.map(s -> key + "." + s)
.map(super::getProperty)
.filter(s -> s != null)
.findFirst()
.orElseGet(()-> super.getProperty(key));
}
There should be only one file, even if it has a lot of properties. Also, there should be only one property for each functionality, like database.host, not database.host and database_host, or anything similar.
You need to create hierarchy for such and for every property in order to know which one will be user. For example, if there is some head global value for database.host, it should be used for that property. If not, check next level in hierarchy, like specific environments (like production value). If such does not exist, check next level, like local or test level. And for bottom level, have a default value. In such way, you have two dimension of consuming properties and as such, decreases chances for error dramatically.
In one company I used to work, we had automated deployer which would handle such level setup, we would just set variable on its web site for level we wanted and it would go from top to bottom and set them. We never had problems with such setup and we would have more then 50 variables in app.properties file.
If not to take into consideration all the redesign methods mentioned in the previous comments, you can wrap all the complexity into Tomtit task manager which is good with these types if tasks.
Just create properties files templates and populate them using environments
steps I do:
I do in code
System.setProperty("myproperty", 1);
and then I set in a shell script the property "myProperty" to 3.
like this:
# setprop "myproperty" 3
and then in the code I try to read the property like this:
System.getProperty("myproperty");
I get the value of 1. which means that the set from the shell didn't actually work.
but when I print all props from shell with
# getprop
I see in the list that myproperty equals 3.
in shorter words: I want to change the value of a property from a script, and I see that this scripts actually changes the property but in the java code I get the old value.
any ideas?
Java code in Android provides System.getProperty and System.setProperty functions in java library but it's important to note that although these java APIs are semantically equal to native version, java version store data in a totally different place. Actually, a hashtable is employed by dalvik VM to store properties. So, java properties are separated, it can't get or set native properties, and neither vice versa.
You can use android.os.SystemProperties class can manipulate native properties, though it's intended for internal usage only. It calls through jni into native property library to get/set properties.
getprop/setprop work on android.os.SystemProperties, not on java.lang.System.
Unfortunately, this class is not available to third party application. Apparently you have rooted your device, so you may still access it.
You can use that snippet to run getProp as shell command and get the value of any property:
private String getSystemProperty(String propertyName) {
String propertyValue = "[UNKNOWN]";
try {
Process getPropProcess = Runtime.getRuntime().exec("getprop " + propertyName);
BufferedReader osRes =
new BufferedReader(new InputStreamReader(getPropProcess.getInputStream()));
propertyValue = osRes.readLine();
osRes.close();
} catch (Exception e) {
// Do nothing - can't get property value
}
return propertyValue;
}
I've a little problem with Websphere application server 7.0 (WAS7) and the reading of Environment Varaibles.
With TomCat, I've defined a variable as
<Environment name="myVar" type="java.lang.String" value="myVarOnServeur"
and I read it with a lookup on the initialContext :
Context ctx = new InitialContext();
String myVar = (String) ctx.lookup( "java:comp/env/myVar" );
and it works!
But with Websphere, I define a environment variable on the GUI but I can't read it in my java code. I've a NamingException.
(source: fullahead.org)
How can I do to fix my problem?
to define inside web.xml
<env-entry>
<env-entry-name>varName</env-entry-name>
<env-entry-value>56</env-entry-value>
<env-entry-type>java.lang.String</env-entry-type>
</env-entry>
to see with java
Context envEntryContext = (Context) new InitialContext().lookup("java:comp/env");
String mydata = (String)envEntryContext.lookup("varName");
You are looking at the wrong place.
You should add the variable in Environment->Naming->Name space bindings->New.
If you choose Binding type String, "Binding identifier" and "Name in namespace..." myVar, you can get variable's value with:
Context ctx = new InitialContext();
String myVar = (String) ctx.lookup( "cell/persistent/myVar" );
On WAS follow the above setting where name is your key and value is your property value. in my example i used Name : Test Value : This is the test value. After setting this values restart your application server. on your Java code call System.getProperty("TEST") where test is the name for your property and the value will show
You can put something like the following in your web.xml file, which should be in your application's WEB-INF directory:
<env-entry>
<env-entry-name>myVar</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>myVarOnServeur</env-entry-value>
</env-entry>
By the way this is a standard syntax and should work across all the application servers. I'm using it with WebSphere, JBoss and WebLogic. It can be queried exactly as you do in your example.
If what you want is to define and manage your own variables, have a look at Environment->Naming->Name space bindings. You can bind jndi names to String constants there. see String binding settings
You should be able to resolve these via WebSphere's AdminOperations MBean:
//sample code from WAS 7 Infocenter
private String expandVariable(String s) throws
javax.management.JMException {
com.ibm.websphere.management.AdminService as =
com.ibm.websphere.management.AdminServiceFactory.getAdminService();
String server = as.getProcessName();
String mBeanName = "*:*,type=AdminOperations,process=" + server;
java.util.Set result = as.queryNames(
new javax.management.ObjectName(mBeanName) , null);
return (String) as.invoke((javax.management.ObjectName)
result.iterator().next(),
"expandVariable",
new Object[]{"${"+s+"}"},
new String[]{"java.lang.String"});
}
See Creating, editing and deleting WebSphere variables.
Websphere 7.0 - 8.5
Set Variable
Admin Console ---> Websphere Application servers -----> Your_sever_name ---> Java and process management ---> Process definition -->Java Virtual Machine --> Custom properties
Get Value in Java
System.getProperty("Your_Variable")
I would just like to elaborate on creating a variable in WebSphere that can be used by a Java app, to hopefully help others, as I had to do a bit of additional research to figure this out.
Let's say you want to create a variable in WebSphere named ENV which contains a value of dev (or int, or prod, or any other value).
In the left panel of the WebSphere admin console, select Servers >
Server Types > WebSphere application servers.
Select the application server that contains the app.
Expand Java and Process Management and select process definition.
Select Java Virtual Machines.
Select Custom properties.
Select New.
Create the name and value of the variable and select OK.
Select Save.
Restart the application server for this change to take effect.
In this example, a variable named ENV with a vaule of "dev" was created.
Next, the Java app will need to be configured to use the ENV variable in WebSphere. In the below markup, the Java app has a class named "Environment". This class creates a variable named env. System.getProperty("ENV") is the magic that gets the variable from WebSphere. It is noteworthy that this Java code should also work with other application servers, such as JBoss or Tomcat, so you don't need to customize the Java code to a particular platform.
While definitely not required, I also am returning env. I am just doing this for demonstration, so that we can get the variable in a JSP page, so that we can see the variables with our own eyes in a JSP page, for validation that this works as expected.
package com.example.main;
public class Environment {
public String env;
public Environment() {
env = System.getProperty("ENV");
}
public String getEnvironment(){
return env;
}
}
Inside of the tags of a JSP page, I add the following markup to get the env variable from the Environment class, which in turn gets the ENV variable from WebSphere.
<%#page import="com.sample.main.Environment"%>
<%
Environment foo = new Environment();
String env = foo.getEnvironment();
out.print("Environment : " + env;
%>
Now, once the app has been deployed to WebSphere, the environment should be displayed, which is how I know that I was able to successfully get the variable from the application server.
The thread is kind of old but just wanted to provide some info. This is with WebSphere 8.5.5
I tried getting WebSphere variables defined in the console via [Environment > WebSphere variables] using
System.getProperty("Variable");
It did not give the variable to me. I looked around a bit on the web and came across the following:
https://www.setgetweb.com/p/WAS855/ae/was2873.html
The following function listed there returns the variables
private static String expandVariable(String s) throws
javax.management.JMException
{
com.ibm.websphere.management.AdminService as = com.ibm.websphere.management.AdminServiceFactory.getAdminService();
String server = as.getProcessName();
java.util.Set result = as.queryNames(new javax.management.ObjectName("*:*,"
+ "type=AdminOperations,process=" + server), null);
return (String)as.invoke((javax.management.ObjectName) result.iterator().next(),"expandVariable",
new Object[] {"${"+s+"}"}, new String[] {"java.lang.String"});
}
Then call
expandVariable("Variable");
I don't see anything there that says that those entries can be read via ctx.lookup( "java:comp/env/..." );
Is it possible to initialize Java system properties using some sort of configuration file?
(ie: can you set java.library.path using a file inside your jar)
EDIT: Clarification: I am asking specifically about initializing the system properties to a value in a file, not setting them later from inside the virtual machine. Yes, you can change system properties to whatever you want very easily after the machine starts up, but the Java system classes will not use the new values.
Practically speaking, this means System.setProperty and System.setProperties are useless for loading native libraries, as JNI will always use the original value of java.library.path to load libraries with. I'm trying to figure out if there's a cleaner alternative to just putting -Djava.library.path=whatever in start up scripts everywhere.
There is a way to set java.library.path programatically, see this.
The code is a hack to set the sys_path field on the ClassLoader,
System.setProperty( "java.library.path", "/path/to/libs" );
Field fieldSysPath = ClassLoader.class.getDeclaredField( "sys_paths" );
fieldSysPath.setAccessible( true );
fieldSysPath.set( null, null );
It would be pretty simple to do yourself:
public static void main(String[] args) {
Properties p = new Properties();
p.load(...); // Load the properties from a file in your jar
for (String name : p.stringPropertyNames()) {
String value = p.getProperty(name);
System.setProperty(name, value);
}
}
Use JAVA_TOOL_OPTIONS environment variable. _JAVA_OPTIONS may also work, but it's not documented at all. The JAVA_TOOL_OPTIONS is weakly documented, but here are some links:
http://www.oracle.com/technetwork/java/javase/envvars-138887.html#gbmsy
https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/envvars002.html
https://bugs.openjdk.java.net/browse/JDK-4971166
An example of use:
set JAVA_TOOL_OPTIONS=-Dmy.property=sth
When launching JVM you should see a message:
Picked up JAVA_TOOL_OPTIONS: ...
You cannot initialize them as far as I know, but you can definitely override their values to anything you want, using any source you wish.
I think the reason why System.setProperty(key,value) is useless for java.library.path in your program is your application is started, you need set it before your program is running.
Check your native library, if the library have any dependency that not included in the java.library.path, System.load will fail, as if System.setProperty(key, value) does not work as expected.
You can parse properties file with additional code (another instance of JVM), redirect its output to var and use this variable as parameter to your java invocation. For example:
public class SystemPropertyLoader {
public static void main(String[] args) throws Exception {
String file = args[0];//TODO check args
Properties properties = new Properties();
InputStream is = new FileInputStream(file);
properties.load(is);
is.close();
StringBuilder builder = new StringBuilder();
for (Entry e : properties.entrySet()){
builder.append("-D");
builder.append(e.getKey());
builder.append('=');
builder.append(e.getValue());
builder.append(' ');
}
System.out.println(builder.toString());
}
}
and
#!/bin/ksh
properties_variable=$(java SystemPropertyLoader input.properties)