I would like to use an alternate list delimiter in Apache Commons Configuration. However, despite trying a great many different ways of accessing a Configuration object and setting its list delimiter, I can never get it to actually use anything other than the default comma delimiter.
I'm using Commons Configuration version 1.8.0 with Java 1.6.0_29 on Mac OS X.
EDIT:
I need to load an XML configuration definition file that establishes a four-tier set of configuration sources:
<configuration>
<system/>
<properties fileName="top.properties"/>
<properties fileName="bop.properties"/>
<properties fileName="fop.properties"/>
</configuration>
... which I supply to the DefaultConfigurationBuilder constructor:
val configBuilder = new DefaultConfigurationBuilder(configURL)
I've tried both overloads of getConfiguration on the resulting DefaultConfigurationBuilder. The zero-arg version is declared to return Configuration, not a sub-class of AbstractConfiguration (though it can be cast to AbstractConfiguration) and Configuration does not even define setListDelimiter.
Can you clarify how, given these requirements, I can get control over the list delimiter?
FOLLOW-UP:
Calling AbstractConfiguration.setDefaultListDelimiter(listDelim) solved the problem for me.
Randall Schulz
To change the list delimiter for a single configuration object use the method setListDelimiter().
To change the list delimiter for all configurations, use the static method setDefaultListDelimiter().
Changing the delimiter will only affect new configuration parsings. So either refresh() your configuration or load your configuration file only after setting the list delimiter.
Example
ListDelimiterDemo.java
import org.apache.commons.configuration.AbstractFileConfiguration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
public class ListDelimiterDemo {
public static void main(String[] args) throws ConfigurationException {
AbstractFileConfiguration config = new PropertiesConfiguration();
config.setListDelimiter('|');
config.load("config.properties");
for (Object listItem : config.getList("myprop")) {
System.out.println(listItem);
}
}
}
config.properties:
myprop=hello|world|!
Prints:
hello
world
!
Related
Problem:
I have 3 parts in the software:
Client A service
Client B service
Target C service
I want to connect to C from A and B
I wrote a library with following setup:
/src/main/java/pkg.../TargetConnector.java
/src/main/java/pkg.../TargetConfig.java
/src/main/resources/application-dev.properties
/src/main/resources/application-tst.properties
/src/main/resources/application-prd.properties
My clients A and B both have there own sources and properties:
/src/main/java/pkg.../Client{A/B}Service.java
/src/main/java/pkg.../Client{A/B}Config.java
/src/main/resources/application-dev.properties
/src/main/resources/application-tst.properties
/src/main/resources/application-prd.properties
The properties of the Connector contains some login info for the service e.g.
target.url=https://....
target.usr=blablabla
target.key=mySHAkey
which is used in the TargetConfig to preconfigure the Connector e.g.
#Value("target.url")
String url;
#Value("target.usr")
String usr;
#Value("target.key")
String key;
#Bean
public TargetConnector connector() {
return new TargetConnector(url, usr, key);
}
Now when I use the connector jar in the client I can find the configuration via packagescan. The connector class is loaded but the problem is that it does not load the properties files.
Research
I found that multiple property files cannot have the same name (e.g. clients application-{profile}.properties clashes with the one from the connector), so I tried to rename application-{profile}.properties of the targetConnector to application-connector-{profile}.properties.
The properties whoever still do not get loaded, (which makes sense since I do not have a e.g connector-dev profile but my profile is simply named dev).
Furthermore, even if I try to explicitly load one of the property files from the connector with:
#PropertySource({"classpath*:application-connector-dev.properties"})
it cannot be found
Question
My question is actually 3 tiered:
How can I load a property file in a dependency jar at all?
How can I load the profiled version of the property file if the the properties file has a different name than application.properties? e.g. application-connector.properties
How can i combine the answers from question 1 and 2 to load the profiled version of the property in the jar?
If further explanation is needed, please ask.
Answer
I went for an approach as given in the accepted answer.
I Just created 3 configs for the dev, tst, prd profiles containing the values needed and annotated the config files with the correct profiles.
You are using #Configuration annotated class. Maybe you can have one per profile. Here you are an example:
#Configuration
#Profile("profileA")
#PropertySource({"classpath:application-profileA.properties"})
public class ConfigurationProfileA{
#Value("${target.url}")
String url;
#Value("${target.usr}")
String usr;
#Value("${target.key}")
String key;
#Bean
public TargetConnector connector() {
return new TargetConnector(url, usr, key);
}
}
Do the same for profile B (maybe you can structure this better but the key points here are the annotation #Profile("") and #PropertySource(""))
Once you have your config class, Spring will use the Configuration class you want by just filling -spring.profiles.active=profileA (or the name of the profile you have written in the #Profile("") annotation)
I think there is a typo in this line #PropertySource({"classpath*:application-connector-dev.properties"})
Please check by removing the asterik.
In order to run with a specific profile, you can run with option -spring.profiles.active=dev for example
If you don’t run with a profile, it will load the default profile in application.properties that you don’t seem to have.
Furthermore, an advice would be to always have an application.properties and put in it the common properties and the default values that you would override in other properties files.
Other mistake is how you assign properties with #Value annotation, you need to use #Value("${PROPERTY_FROM_PROPERTIES_FILE}")
I want to use a yml configuration file in my project. I am using jackson-dataformat-yaml for parsing yml files. But I need to parse yml comments as well. I used the similar approach in python using ruamel yaml. How can I do the same in java?
Upd.
What for? Well, I wanted to make it possible to override my configuration options by using command line arguments. So, to generate description message for each option, I wanted to use my comments. Like this:
In my config.yml
# Define a source directory
src: '/foo/bar'
# Define a destination directory
dst: '/foo/baz'
So when you run your program with the --help flag, you'll see the following output:
Your program can be ran with the following options:
--src Define a source directory
--dst Define a destination directory
The main benefit in such a model is that you don't ever need to repeat the same statement twice, because they can be retrieved from the configuration file.
Basically, you have three layers of data:
Your configuration schema. This defines the values that are to be defined in the configuration file.
The configuration file itself, which describes the usual configuration on the current machine.
One-time switches, which override the usual configuration.
The descriptions of what each value does belong to the schema, not to the configuration file itself. Think about it: If someone edits the configuration file on their machine and changes the comments, your help output would suddenly show different descriptions.
My suggestion would be to add the descriptions to the schema. The schema is the Java class you load your YAML into. I am not sure why you are using Jackson, since it uses SnakeYaml as parser and SnakeYaml is perfectly able to deserialize into Java classes, but has more configuration options since it does not generalize over JSON and YAML like Jackson does.
Here's a general idea how to do it with SnakeYaml (beware, untested):
// ConfigParam.java
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface ConfigParam { String description(); }
// Configuration.java
public class Configuration {
#ConfigParam("Define a source directory")
String src;
#ConfigParam("Define a destination directory")
String dst;
}
// loading code
Yaml yaml = new Yaml(new Constructor(Configuration.class));
Configuration config = yaml.loadAs(input, Configuration.class);
// help generation code
System.out.println("Your program can be ran with the following options:")
for (Field field: Configuration.class.getFields()) {
ConfigParam ann = field.getAnnotation(ConfigParam.class);
if (ann != null) {
System.out.println(String.format("--%s %s", field.getName(), ann.description());
}
}
For mapping actual parameters to the configuration, you can also loop over class fields and map the parameters to the field names after having loaded the configuration (to replace the standard values with the given ones).
I'm using Spring 3.1 and bootstrapping an application using the #Configuration and #ComponentScan attributes.
The actual start is done with
new AnnotationConfigApplicationContext(MyRootConfigurationClass.class);
This Configuration class is annotated with
#Configuration
#ComponentScan("com.my.package")
public class MyRootConfigurationClass
and this works fine. However I'd like to be more specific about the packages I scan so I tried.
#Configuration
#ComponentScan("com.my.package.first,com.my.package.second")
public class MyRootConfigurationClass
However this fails with errors telling me it can't find components specified using the #Component annotation.
What is the correct way to do what I'm after?
Thanks
#ComponentScan uses string array, like this:
#ComponentScan({"com.my.package.first","com.my.package.second"})
When you provide multiple package names in only one string, Spring interprets this as one package name, and thus can't find it.
There is another type-safe alternative to specifying a base-package location as a String. See the API here, but I've also illustrated below:
#ComponentScan(basePackageClasses = {ExampleController.class, ExampleModel.class, ExmapleView.class})
Using the basePackageClasses specifier with your class references will tell Spring to scan those packages (just like the mentioned alternatives), but this method is both type-safe and adds IDE support for future refactoring -- a huge plus in my book.
Reading from the API, Spring suggests creating a no-op marker class or interface in each package you wish to scan that serves no other purpose than to be used as a reference for/by this attribute.
IMO, I don't like the marker-classes (but then again, they are pretty much just like the package-info classes) but the type safety, IDE support, and drastically reducing the number of base packages needed to include for this scan is, with out a doubt, a far better option.
Provide your package name separately, it requires a String[] for package names.
Instead of this:
#ComponentScan("com.my.package.first,com.my.package.second")
Use this:
#ComponentScan({"com.my.package.first","com.my.package.second"})
Another way of doing this is using the basePackages field; which is a field inside ComponentScan annotation.
#ComponentScan(basePackages={"com.firstpackage","com.secondpackage"})
If you look into the ComponentScan annotation .class from the jar file you will see a basePackages field that takes in an array of Strings
public #interface ComponentScan {
String[] basePackages() default {};
}
Or you can mention the classes explicitly. Which takes in array of classes
Class<?>[] basePackageClasses
You use ComponentScan to scan multiple packages using
#ComponentScan({"com.my.package.first","com.my.package.second"})
You can also use #ComponentScans annotation:
#ComponentScans(value = { #ComponentScan("com.my.package.first"),
#ComponentScan("com.my.package.second") })
I use:
#ComponentScan(basePackages = {"com.package1","com.package2","com.package3", "com.packagen"})
make sure you have added this dependency in your pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Use
#ComponentScan(basePackages = {"package1", "package2"})
define it at top before class.
Edit: the brackets must be around all the base packages, not a pair of brackets per package
I have got a corrupted property file from customer. The file is modified by application to update version number. The code uses apache commons configuration. When I tested, the library always seems to write files in iso-8859-1 format.
Code is simplified to below. What is the possibility of following code write bad file?
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import java.io.FileWriter;
import java.io.IOException;
public class TestConfig {
public void editVersionInfo() throws ConfigurationException, IOException {
String filename = "C:\\temp\\VersionProperties\\Version.properties";
PropertiesConfiguration config = new PropertiesConfiguration(filename);
config.setProperty("application.version", "2011");
config.save(new FileWriter(filename));
}
public static void main(String[] args) throws IOException, ConfigurationException {
TestConfig tc = new TestConfig();
tc.editVersionInfo();
}
}
Just in case - the bad file looks like below. It does not look like in any encoding. The file originally was normal property file with keys and values all in English(ascii chars).
F????Co?aR??m??E?3#?? =
h\u00BD5j\u00B3\u00E0\u0096\u001D\u0081fe\u00BEo\b\u00A3\u0001\u00FE\u00A4\u00DE\u0000\u00FBi\"\u009C{\u00FC\u00D9\u00E2?c\u00F6\u00FF%B\u00A47\u00195\u001EXv\u0097/\u00D7x\u0099\u000E\u00A2gIX\u0014\u0097]k\u00882\u0003\u0014\u0097\u00BC\u00C3\u00AE\u00B4\u001E\u00B3R\u00E4\u00DE&\u0000\u0016\u009B\"7\u0085'\"\u00DCT*v'\u0092\u0007\u0091A\u00BD\u00ACl6~\u0097\u00C0\u00B1\u00D1\u00EB\u00FF\u00A8\u00F3\u0001'\u00BF\u0006\u001F\u009C\fk\u009F\u00C2\u00D9L^_\u0004J4\u00AF\u00D8\u00DAW\u00C4\u00CDj\u00E3\u0095\u00D1+\u00CE?\u0004>Z]\u00D7\u000B\u0098\u0016\u0095\u00AC\u00F7\u00E7\u009ATF\u0019\f)\u00A3\u00A9\u00DC\u00AD\u00ACtq5\u0085\u008E-\u00A3oH\u0000\u00C2\u0092\u00B5\u00F2\u008AG\u008F&\u00F5\u0017H\u0003!\u0083\u00B4\u008AV=\u00E0\u00EDj\u00F0\u00D0J\u00DB\u00CC\u00F2O\u00CE\u00BE\u00F0*4\u0006y~\u00C3\u00B7\"\u000B\u00E4\u00C0$>\u00F3\u00F2~\u00CE\u0097#\u00BAc\u00EC#\u00B4\u00AD\u009A\u00BAX\fF\u0083]\u00C2\u00D4\u00AB\u00F3\u009DQ\u0092\u00854z\u0097\u00FDG\t\u0095\u00E3}ty\u0082I\u00C3`\u009E
??
Edit: The customer environment is japanese. How ever the application is always run with
-Dfile.encoding=UTF8
I suspect your customer has a different default character encoding to what you have. Check their setting of the property file.encoding (counterintuitively named, I know).
An alternative possibility is that you have two threads writing that property file. I don't know, but I suspect the Apache library won't be thread-safe by default.
Edit1
I am not sure if the title is best for the problem so if any have some more orinted title please suggest
i am trying my hands on camel where i have to fetch some csv file from a file system and needs to convert it to xml format and place it on some other system
i am using camel for this and here is my sample POC code
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;
import com.poc.convertor.CSVConverterBean;
public class TestPOC {
public static void main(String args[]) throws Exception {
CamelContext context = new DefaultCamelContext();
context.addRoutes(new RouteBuilder() {
public void configure() {
from("file:data/csv?noop=true").unmarshal().csv().bean(new CSVConverterBean(),"processCSVInvoice").to("file:data/csvoutput?fileName=test.xml").marshal("jaxb");
}
});
context.start();
Thread.sleep(1000);
context.stop();
}
}
In this approach camel csv unmarshaller will covert the csv file in to java list List<List<String>>
i have written a java converter CSVConverterBean which will iterate the list and set the values in the respective java objects being generated by jaxb 2.x , final object is being marshaled in to xml and file is being saved.
Everything is being working properly with only one issue , if in future there arises any request to change mapping we need to do the modification in CSVConverterBean and than this java file needs to be recompiled and need to be redistributed which we want to avoid.
my question is, Is there any way by which we can map the values from the java List being given by Camel to the respective java classes being generated by the JaxB so that the need to recompile java code can be avooided.
You can provide a "from-to" kind of configuration file to map the columns of your CSV data with your Bean properties, and code an algorithm to read that file and process the convertion.
You could do with a .properties file:
# mapping.properties
column0=propertyOne
column1=propertyTwo
For each column in your CSV, you get the value from the property file and find which property you should set the value on.
int columnIndex = 0;
for(String column : csvColumns) {
String property = findProperty(columnIndex);
reflectionUtil.setValue(object, property, column);
columnIndex++;
}
This may give you some hint.
Whenever your data changes, you will only need to change the property file, not the class.
Use Bruno's idea, but read the property names from the header row in the csv file.
I have solved this problem using dom4j.camel gave me back csv as list> and firstly i read the headers and than made these headers and the XML tags and the values as there respected values on run time.