Freemarker: read some template parameters programmatically - java

I am trying to read some freemarker template values programmatically from Java as follows:
public Map<String, Object> getRootAssignments() {
Enumeration en = template.getRootTreeNode().children();
Map<String, Object> map = new HashMap<>();
while (en.hasMoreElements()) {
Object next = en.nextElement();
// instanceof private class does not work
if (next.getClass().getSimpleName().equals("Assignment")) {
map.put(getInternalState(next, "variableName").toString(),
getInternalState(next, "value").toString());
}
}
return map;
}
private Object getInternalState(Object o, String fieldName) {
Field field = null;
boolean wasAccessible = false;
try {
field = o.getClass().getDeclaredField(fieldName);
wasAccessible = field.isAccessible();
field.setAccessible(true);
return field.get(o);
} catch (Exception e) {
return "";
} finally {
if (field != null) {
field.setAccessible(wasAccessible);
}
}
}
With this I can easily read <#assign variable = "value"/>. But once I need to get some variable which needs to be evaluated (for instance <#assign variable = "first" + "second"/>), this obviously does not work.
In fact I don't really like this approach, I would prefer to read the variable states AFTER the template rendering, so nothing would have to be evaluated again. Is it feasible?

You can read back variable values if you keep the freemarker.core.Environment alive, like this:
Environment env = myTemplate.createProcessingEnvironment(root, out);
env.process(); // process the template
TemplateModel x = env.getVariable("x");
As of your initial approach, note that you are using API-s that were marked is internal, means no long term backward compatibility guarantees. But what's really brittle is reading private fields with reflection. Then calling TemplateObject.getParameterCount/getParameterRole/getParameterValue is more stable, as that's at least an API, even if internal.

Related

Is there a way to get java.util.logging.LogManager to report all loggers for which properties are specified or to access the properties?

In LogManager, the method getLoggerNames appears to only return loggers that have been actually instantiated already. However, logging properties can be held "in reserve" until a logger with a given name is instantiated.
Is there a way to get the full list of loggers for which we have settings, or to at least get the current properties set/map, without reading the original file from my own code?
JDK-8033661: readConfiguration does not cleanly reinitialize the logging system was fixed in Java version 9 which added the LogManager.updateConfiguration(Function<String,BiFunction<String,String,String>>) method. Per the documentation this method will read configuration keys and returns a function whose returned value will be applied to the resulting configuration. By supplying an identity function you can iterate the existing configuration keys instead of the actual created loggers by doing something like the following:
Function<String, BiFunction<String,String,String>> consume
= new Function<String, BiFunction<String,String,String>>() {
#Override
public BiFunction<String, String, String> apply(final String k) {
return new BiFunction<String, String, String>() {
#Override
public String apply(String o, String n) {
System.out.println(k +"="+ o);
return o;
}
};
}
};
LogManager.getLogManager().updateConfiguration(consume);
For JDK 8 and older, you have to do one of the following:
Read the logging.properties file yourself.
Override the LogManager.readConfiguration(InputStream) to capture the bytes from the stream and create your own Properties object from the stream. The no arg readConfiguration will call this method so the given stream is the properties file as bytes.
Resort to reflection (yuck!).
The easy way to read the properties file is by using the java.util.Properties class.
final Properties props = new Properties();
try {
String v = System.getProperty("java.util.logging.config.file");
if (v == null) {
v = System.getProperty("java.home") + "/lib/logging.properties";
}
final File f = new File(v).getCanonicalFile();
final InputStream in = new FileInputStream(f);
try {
props.load(in);
} finally {
in.close();
}
} catch (final RuntimeException permissionsOrMalformed) {
} catch (final Exception ioe) {
}

How to read file using groovy and store its contents are variables?

I'm looking for groovy specific way to read file and store its content as different variables. Example of my properties file:
#Local credentials:
postgresql.url = xxxx.xxxx.xxxx
postgresql.username = xxxxxxx
postgresql.password = xxxxxxx
console.url = xxxxx.xxxx.xxx
At the moment I'm using this java code to read the file and use variables:
Properties prop = new Properties();
InputStream input = null;
try {
input = new FileInputStream("config.properties");
prop.load(input);
this.postgresqlUser = prop.getProperty("postgresql.username")
this.postgresqlPass = prop.getProperty("postgresql.password")
this.postgresqlUrl = prop.getProperty("postgresql.url")
this.consoleUrl = prop.getProperty("console.url")
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
}
}
}
}
My colleague recommended to use groovy way to deal with this and mentioned streams but I can't seem to find much information about on how to store data in separate variables, what I know so far is that def text = new FileInputStream("config.properties").getText("UTF-8") could read whole file and store it in one variable, but not separate. Any help would be appreciated
If you're willing to make your property file keys and class properties abide by a naming convention, then you can apply the property file values quite easily. Here's an example:
def config = '''
#Local credentials:
postgresql.url = xxxx.xxxx.xxxx
postgresql.username = xxxxxxx
postgresql.password = xxxxxxx
console.url = xxxxx.xxxx.xxx
'''
def props = new Properties().with {
load(new StringBufferInputStream(config))
delegate
}
class Foo {
def postgresqlUsername
def postgresqlPassword
def postgresqlUrl
def consoleUrl
Foo(Properties props) {
props.each { key, value ->
def propertyName = key.replaceAll(/\../) { it[1].toUpperCase() }
setProperty(propertyName, value)
}
}
}
def a = new Foo(props)
assert a.postgresqlUsername == 'xxxxxxx'
assert a.postgresqlPassword == 'xxxxxxx'
assert a.postgresqlUrl == 'xxxx.xxxx.xxxx'
assert a.consoleUrl == 'xxxxx.xxxx.xxx'
In this example, the property keys are converted by dropping the '.' and capitalizing the following letter. So postgresql.url becomes postgresqlUrl. Then it's just a matter for iterating through the keys and calling setProperty() to apply the value.
Take a look at the ConfigSlurper:
http://mrhaki.blogspot.de/2009/10/groovy-goodness-using-configslurper.html

put/add is replacing all entires in a map or list

I am trying to convert a map: Map<String, Map<String, Map<String, Map<String, String>>>>to a Map<String, Settings>.
The Settings class contains all the possible map keys and will be set to true when looping through this particular key.
The problem is when in the deepest map, when adding to a global Map<String, Settings>, the Settings will be replaved with the last Settings for every entry.
Can someone help me find out where i do wrong?
public void loop(Map map, Settings settings){
List keys = new ArrayList(map.keySet());
if(map.get(keys.get(0)) instanceof Map){
//is a map, so continue loop + add to vorm
for(int i = 0; i < keys.size(); i++){
Settings tmp = settings;
String field = keys.get(i).toString();
Method method = null;
try {
//Set some booleans for key
method = tmp.getClass().getMethod(field, boolean.class);
method.invoke(tmp, true);
loop((Map) map.get(keys.get(i).toString()), tmp);
} catch (Exception e) {
e.printStackTrace();
}
}else{
for(int i = 0; i < keys.size(); i++) {
Settings tmp = settings;
String key = keys.get(i).toString();
String word = map.get(key).toString();
tmp.setWord(word);
Settings input = tmp;
settingsList.add(convert, input);//put into 2 arraylists
keyList.add(convert, woord);
convert++;
//vormen.put(word, tmp);//put into list
}
}
}
This method is called here:
public void convert(){
vormen = new HashMap();
settingsList = new ArrayList<>();
wordList = new ArrayList<>();
if(jsonMap.isEmpty()){
throw new NullPointerException("You are trying to convert a null map");
}else {
loop(jsonMap, new Settings());
}
}
Not every variable might be correctly named, i just renamed them.
Thanks for you help
EDIT: fixed, the temporary Settings that was put into the arrays was somehow being changed every time, acting like a pointer or something. I made a new Settings just before adding, and set the settings of tmp to that one. It works now.

ResourceTool : to recover a jpg with ResourceNode [velocity]

During my searching, I would like a piece of advice about the following situation :
the guy, on my website, choose a parcel to send, when he validates the choice, some carriers appear as results. Now some carriers have different offers with different logos located in a special directory.
Now the business logic I would like to use is :
If in the directory I find the peculiar logo corresponding to the special offer, I will take the logo to display It in the web page with the special offer.
I choose to do this work with the ResourceTool from Velocity
I have to implement 2 methods getLogo() and getLabel().
The getLogo() will look for the special logo.
I think to use this method to recover the object :
public static ResourceNode getResource(Context context, ResourceType resourceType, String...keys) {
try {
if (null != ResourcesTool.instance) {
ResourceNode resource = ResourcesTool.instance.getResourceSet(context, resourceType);
if (null != resource) {
Deque < String > keyDeque = new ArrayDeque < > ();
for (String key: keys) {
keyDeque.add(key);
}
return (ResourceNode) resource.sub(keyDeque);
}
}
} catch (Exception e) {
BoxtaleLogger.debug("[ResourcesTool.getResource] error: ", e);
}
return null;
}
Now I am searching a example to merely use this method to recover the different .jpg
question 2 : I don't understand what is the meaningful of Context context in this method ?
Then the resourceType is an enum either a String or a picture (the logo in fact)
All right I found It :
public String getLogo(ResourceNode node){
//readable variables
String ope_code = (String)((Instance)get("operateur")).get("ope_code");
String paysDest = ((Instance)db.getEntity("tab_pays").fetch((Integer) get("pz_id"))).get("pz_iso");
String path = node.get(ope_code+"_"+get("srv_code")+"_"+paysDest+".png");
if (path=null){
path = node.get(ope_code+"_"+get("srv_code")+".png");
if (path=null){
path = node.get(ope_code+".png");
}
}
return path ;
}
Now I am testing the method, I will tell you after.

How can I write Java properties in a defined order?

I'm using java.util.Properties's store(Writer, String) method to store the properties. In the resulting text file, the properties are stored in a haphazard order.
This is what I'm doing:
Properties properties = createProperties();
properties.store(new FileWriter(file), null);
How can I ensure the properties are written out in alphabetical order, or in the order the properties were added?
I'm hoping for a solution simpler than "manually create the properties file".
As per "The New Idiot's" suggestion, this stores in alphabetical key order.
Properties tmp = new Properties() {
#Override
public synchronized Enumeration<Object> keys() {
return Collections.enumeration(new TreeSet<Object>(super.keySet()));
}
};
tmp.putAll(properties);
tmp.store(new FileWriter(file), null);
See https://github.com/etiennestuder/java-ordered-properties for a complete implementation that allows to read/write properties files in a well-defined order.
OrderedProperties properties = new OrderedProperties();
properties.load(new FileInputStream(new File("~/some.properties")));
Steve McLeod's answer used to work for me, but since Java 11, it doesn't.
The problem seemed to be EntrySet ordering, so, here you go:
#SuppressWarnings("serial")
private static Properties newOrderedProperties()
{
return new Properties() {
#Override public synchronized Set<Map.Entry<Object, Object>> entrySet() {
return Collections.synchronizedSet(
super.entrySet()
.stream()
.sorted(Comparator.comparing(e -> e.getKey().toString()))
.collect(Collectors.toCollection(LinkedHashSet::new)));
}
};
}
I will warn that this is not fast by any means. It forces iteration over a LinkedHashSet which isn't ideal, but I'm open to suggestions.
To use a TreeSet is dangerous!
Because in the CASE_INSENSITIVE_ORDER the strings "mykey", "MyKey" and "MYKEY" will result in the same index! (so 2 keys will be omitted).
I use List instead, to be sure to keep all keys.
List<Object> list = new ArrayList<>( super.keySet());
Comparator<Object> comparator = Comparator.comparing( Object::toString, String.CASE_INSENSITIVE_ORDER );
Collections.sort( list, comparator );
return Collections.enumeration( list );
The solution from Steve McLeod did not not work when trying to sort case insensitive.
This is what I came up with
Properties newProperties = new Properties() {
private static final long serialVersionUID = 4112578634029874840L;
#Override
public synchronized Enumeration<Object> keys() {
Comparator<Object> byCaseInsensitiveString = Comparator.comparing(Object::toString,
String.CASE_INSENSITIVE_ORDER);
Supplier<TreeSet<Object>> supplier = () -> new TreeSet<>(byCaseInsensitiveString);
TreeSet<Object> sortedSet = super.keySet().stream()
.collect(Collectors.toCollection(supplier));
return Collections.enumeration(sortedSet);
}
};
// propertyMap is a simple LinkedHashMap<String,String>
newProperties.putAll(propertyMap);
File file = new File(filepath);
try (FileOutputStream fileOutputStream = new FileOutputStream(file, false)) {
newProperties.store(fileOutputStream, null);
}
I'm having the same itch, so I implemented a simple kludge subclass that allows you to explicitly pre-define the order name/values appear in one block and lexically order them in another block.
https://github.com/crums-io/io-util/blob/master/src/main/java/io/crums/util/TidyProperties.java
In any event, you need to override public Set<Map.Entry<Object, Object>> entrySet(), not public Enumeration<Object> keys(); the latter, as https://stackoverflow.com/users/704335/timmos points out, never hits on the store(..) method.
In case someone has to do this in kotlin:
class OrderedProperties: Properties() {
override val entries: MutableSet<MutableMap.MutableEntry<Any, Any>>
get(){
return Collections.synchronizedSet(
super.entries
.stream()
.sorted(Comparator.comparing { e -> e.key.toString() })
.collect(
Collectors.toCollection(
Supplier { LinkedHashSet() })
)
)
}
}
If your properties file is small, and you want a future-proof solution, then I suggest you to store the Properties object on a file and load the file back to a String (or store it to ByteArrayOutputStream and convert it to a String), split the string into lines, sort the lines, and write the lines to the destination file you want.
It's because the internal implementation of Properties class is always changing, and to achieve the sorting in store(), you need to override different methods of Properties class in different versions of Java (see How to sort Properties in java?). If your properties file is not large, then I prefer a future-proof solution over the best performance one.
For the correct way to split the string into lines, some reliable solutions are:
Files.lines()/Files.readAllLines(), if you use a File
BufferedReader.readLine() (Java 7 or earlier)
IOUtils.readLines(bufferedReader) (org.apache.commons.io.IOUtils, Java 7 or earlier)
BufferedReader.lines() (Java 8+) as mentioned in Split Java String by New Line
String.lines() (Java 11+) as mentioned in Split Java String by New Line.
And you don't need to be worried about values with multiple lines, because Properties.store() will escape the whole multi-line String into one line in the output file.
Sample codes for Java 8:
public static void test() {
......
String comments = "Your multiline comments, this should be line 1." +
"\n" +
"The sorting should not mess up the comment lines' ordering, this should be line 2 even if T is smaller than Y";
saveSortedPropertiesToFile(inputProperties, comments, Paths.get("C:\\dev\\sorted.properties"));
}
public static void saveSortedPropertiesToFile(Properties properties, String comments, Path destination) {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
// Storing it to output stream is the only way to make sure correct encoding is used.
properties.store(outputStream, comments);
/* The encoding here shouldn't matter, since you are not going to modify the contents,
and you are only going to split them to lines and reorder them.
And Properties.store(OutputStream, String) should have translated unicode characters into (backslash)uXXXX anyway.
*/
String propertiesContentUnsorted = outputStream.toString("UTF-8");
String propertiesContentSorted;
try (BufferedReader bufferedReader = new BufferedReader(new StringReader(propertiesContentUnsorted))) {
List<String> commentLines = new ArrayList<>();
List<String> contentLines = new ArrayList<>();
boolean commentSectionEnded = false;
for (Iterator<String> it = bufferedReader.lines().iterator(); it.hasNext(); ) {
String line = it.next();
if (!commentSectionEnded) {
if (line.startsWith("#")) {
commentLines.add(line);
} else {
contentLines.add(line);
commentSectionEnded = true;
}
} else {
contentLines.add(line);
}
}
// Sort on content lines only
propertiesContentSorted = Stream.concat(commentLines.stream(), contentLines.stream().sorted())
.collect(Collectors.joining(System.lineSeparator()));
}
// Just make sure you use the same encoding as above.
Files.write(destination, propertiesContentSorted.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
// Log it if necessary
}
}
Sample codes for Java 7:
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
......
public static void test() {
......
String comments = "Your multiline comments, this should be line 1." +
"\n" +
"The sorting should not mess up the comment lines' ordering, this should be line 2 even if T is smaller than Y";
saveSortedPropertiesToFile(inputProperties, comments, Paths.get("C:\\dev\\sorted.properties"));
}
public static void saveSortedPropertiesToFile(Properties properties, String comments, Path destination) {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
// Storing it to output stream is the only way to make sure correct encoding is used.
properties.store(outputStream, comments);
/* The encoding here shouldn't matter, since you are not going to modify the contents,
and you are only going to split them to lines and reorder them.
And Properties.store(OutputStream, String) should have translated unicode characters into (backslash)uXXXX anyway.
*/
String propertiesContentUnsorted = outputStream.toString("UTF-8");
String propertiesContentSorted;
try (BufferedReader bufferedReader = new BufferedReader(new StringReader(propertiesContentUnsorted))) {
List<String> commentLines = new ArrayList<>();
List<String> contentLines = new ArrayList<>();
boolean commentSectionEnded = false;
for (Iterator<String> it = IOUtils.readLines(bufferedReader).iterator(); it.hasNext(); ) {
String line = it.next();
if (!commentSectionEnded) {
if (line.startsWith("#")) {
commentLines.add(line);
} else {
contentLines.add(line);
commentSectionEnded = true;
}
} else {
contentLines.add(line);
}
}
// Sort on content lines only
Collections.sort(contentLines);
propertiesContentSorted = StringUtils.join(IterableUtils.chainedIterable(commentLines, contentLines).iterator(), System.lineSeparator());
}
// Just make sure you use the same encoding as above.
Files.write(destination, propertiesContentSorted.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
// Log it if necessary
}
}
True that keys() is not triggered so instead of passing trough a list as Timmos suggested you can do it like this:
Properties alphaproperties = new Properties() {
#Override
public Set<Map.Entry<Object, Object>> entrySet() {
Set<Map.Entry<Object, Object>> setnontrie = super.entrySet();
Set<Map.Entry<Object, Object>> unSetTrie = new ConcurrentSkipListSet<Map.Entry<Object, Object>>(new Comparator<Map.Entry<Object, Object>>() {
#Override
public int compare(Map.Entry<Object, Object> o1, Map.Entry<Object, Object> o2) {
return o1.getKey().toString().compareTo(o2.getKey().toString());
}
});
unSetTrie.addAll(setnontrie);
return unSetTrie;
}
};
alphaproperties.putAll(properties);
alphaproperties.store(fw, "UpdatedBy Me");
fw.close();

Categories

Resources