In part 1 and part 2 we discussed ways of customizing your codebase to deal with varying requirements for each of your customers. Now let’s talk about another technique using property files that will allow you to customize field labels and other text. We’ll also look at some other uses of property files.
Structuring property files
We will create a default properties file as well as one for every customer that wants to override the defaults. And we’re going to place them inside of the resources folder. You can visual it like this:
txt_content.properties will be our default file. Each customer can override as much or as little as they need to. The customer files will follow the format of “txt_content_{CUSTOMER}.properties”.
Creating the Wicket ResourceLoader
Now we need to create a class that will tell Wicket how to use these files. It might seem like there is a lot going on, but we’ll see that it’s actually quite simple.
public class TxtContentResourceLoader implements IStringResourceLoader {
private static final Logger log = LoggerFactory.getLogger(TxtContentResourceLoader.class);
private Properties fileProps = null;
public TxtContentResourceLoader() {
super();
initProperties();
}
protected String getIdentifier() {
return "labels/txt_content";
}
private void initProperties() {
if(fileProps != null) {
return;
}
java.util.Properties merged = new java.util.Properties();
java.util.Properties defaultProps = getProperties(getIdentifier() + ".properties");
if(defaultProps != null) {
merged.putAll(defaultProps);
}
String customer = Utils.getCustomer();
if(customer != null) {
java.util.Properties clientProps = getProperties(getIdentifier()+ '_' +customer+".properties");
if(clientProps != null) {
merged.putAll(clientProps);
}
}
ValueMap data = new ValueMap();
Enumeration<?> enumeration = merged.propertyNames();
while (enumeration.hasMoreElements()) {
String property = (String)enumeration.nextElement();
data.put(property, merged.getProperty(property));
}
fileProps = new Properties(getIdentifier(), data);
}
private java.util.Properties getProperties(String path) {
try (InputStream in = TxtContentResourceLoader.class.getClassLoader().getResourceAsStream(path)) {
UtfPropertiesFilePropertiesLoader loader = new UtfPropertiesFilePropertiesLoader(null, StandardCharsets.UTF_8.name());
if(in == null) {
return null;
}
return loader.loadJavaProperties(in);
} catch (IOException e) {
log.error("failed getProperties({})", path, e);
}
return null;
}
private String load(String key) {
if(fileProps == null) return null;
String value = fileProps.getString(key);
if (value != null) {
return value;
}
return null;
}
@Override
public String loadStringResource(Class<?> clazz, String key, Locale locale, String style, String variation) {
return load(key);
}
@Override
public String loadStringResource(Component component, String key, Locale locale, String style, String variation) {
return load(key);
}
}
What we’re doing here is:
- Load all the properties from our defaults (txt_content.properties) into a map.
- Next, if there is a file for the current customer, load those properties as well.
- Now we need to combine or merge them into a “merged” map so that the customer-specific key/values overwrite the entries in the default map.
Let’s use it!
To use our new resource loader, we just need to register it with our Wicket application. We do it inside of our init() method for the WebApplication:
getResourceSettings().getStringResourceLoaders().add(new TxtContentResourceLoader());
Let’s define some very simple properties:
txt_content.properties:
user.address.zip.lbl=Zip Code
user.address.state.lbl=State
txt_content_FB.properties:
user.address.zip.lbl=Zip/Postal Code
user.address.state.lbl=State/Provence
To actually use these properties in our web pages we can do something like this:
add(new Label("zip", getLbl("user.address.zip")));
add(new Label("state", getLbl("user.address.state")));
Wicket will now know to use the correct version depending on which customer is set in the JVM parameters.
Other uses
We’ve only looked at a simple example of customizing labels. However, you can use this technique to do a lot more:
- You can allow customers to override the default visibility of fields by using a “hidden” property (user.address.zip.hidden=1)
- You can set validation rules such as not requiring a certain field (user.address.zip.required=0)
- You can allow customers to make some fields read-only (user.address.zip.readOnly=1)
- Use your imagination!
The full source code can be found on GitHub, enjoy!