Velocity template metadata - java

Does Apache Velocity include a mechanism for adding metadata to a template?
I'm trying to add some extra information to my templates (e.g., type and descriptive name), and then read those to programmatically group templates by type, and list the templates on the UI using their descriptive name.
I've tried to use literal #[[...]]# blocks (and parse them), and #set directives, but both a have issues. They are hacky (require some parsing of the template) and far from elegant.

Hmmm, I'm not aware of anything built-in to do this. To avoid processing a whole template on a first pass though, one trick is to conditionally throw an exception (MetadataFinished below) during that pass, but not normal execution.
Clearly this would still need to compile the whole template up front, though this should come in useful at execution time.
E.g.
import org.apache.commons.io.output.NullWriter;
public class Metadata {
private Map<String, Template> byKey = new LinkedHashMap<>();
private Template currentTemplate;
/** Callback from .vm */
public void set(String key) throws MetadataFinished {
// Only do this in addTemplate()
if (currentTemplate != null) {
byKey.put(key, currentTemplate);
throw new MetadataFinished();
}
}
public void addTemplate(Template template) {
currentTemplate = template;
try {
Context context = new VelocityContext();
context.put("metadata", this);
template.merge(context, new NullWriter());
} catch (MetadataFinished ex) {
// Ignored
} finally {
currentTemplate = null;
}
}
public void execute(String key) {
Template template = byKey.get(key);
Context context = new VelocityContext();
PrintWriter pw = new PrintWriter(System.out);
template.merge(context, pw);
pw.flush();
}
// Extends Error to avoid Velocity adding a wrapping MethodInvocationException
private static class MetadataFinished extends Error {
}
public static void main(String[] args) {
Metadata metadata = new Metadata();
VelocityEngine engine = new VelocityEngine();
engine.setProperty("file.resource.loader.path", "/temp");
engine.init();
String[] fileNames = { "one.vm", "two.vm" };
for (String fileName : fileNames) {
Template template = engine.getTemplate(fileName);
metadata.addTemplate(template);
}
metadata.execute("vm1");
metadata.execute("vm2");
}
}
Then in one.vm:
$!metadata.set("vm1")##
-----------
This is VM1
-----------
The ## there is a bit ugly - it's just to stop a blank line being output. If readability is important, this can be made a bit neater with a macro though:
#metadata("vm2")
-----------
This is VM2
-----------
That macro could be defined in the global VM_global_library.vm:
#macro( metadata $key )
$!metadata.set($key)#end
Just for reference, the output is as expected:
-----------
This is VM1
-----------
-----------
This is VM2
-----------

Related

Spring batch FlatFileItemWriter write as csv from Object

I am using Spring batch and have an ItemWriter as follows:
public class MyItemWriter implements ItemWriter<Fixing> {
private final FlatFileItemWriter<Fixing> writer;
private final FileSystemResource resource;
public MyItemWriter () {
this.writer = new FlatFileItemWriter<>();
this.resource = new FileSystemResource("target/output-teste.txt");
}
#Override
public void write(List<? extends Fixing> items) throws Exception {
this.writer.setResource(new FileSystemResource(resource.getFile()));
this.writer.setLineAggregator(new PassThroughLineAggregator<>());
this.writer.afterPropertiesSet();
this.writer.open(new ExecutionContext());
this.writer.write(items);
}
#AfterWrite
private void close() {
this.writer.close();
}
}
When I run my spring batch job, the items are written to file as:
Fixing{id='123456', source='TEST', startDate=null, endDate=null}
Fixing{id='1234567', source='TEST', startDate=null, endDate=null}
Fixing{id='1234568', source='TEST', startDate=null, endDate=null}
1/ How can I write just the data so that the values are comma separated and where it is null, it is not written. So the target file should look like this:
123456,TEST
1234567,TEST
1234568,TEST
2/ Secondly, I am having an issue where only when I exit spring boot application, I am able to see the file get created. What I would like is once it has processed all the items and written, the file to be available without closing the spring boot application.
There are multiple options to write the csv file. Regarding second question writer flush will solve the issue.
https://howtodoinjava.com/spring-batch/flatfileitemwriter-write-to-csv-file/
We prefer to use OpenCSV with spring batch as we are getting more speed and control on huge file example snippet is below
class DocumentWriter implements ItemWriter<BaseDTO>, Closeable {
private static final Logger LOG = LoggerFactory.getLogger(StatementWriter.class);
private ColumnPositionMappingStrategy<Statement> strategy ;
private static final String[] columns = new String[] { "csvcolumn1", "csvcolumn2", "csvcolumn3",
"csvcolumn4", "csvcolumn5", "csvcolumn6", "csvcolumn7"};
private BufferedWriter writer;
private StatefulBeanToCsv<Statement> beanToCsv;
public DocumentWriter() throws Exception {
strategy = new ColumnPositionMappingStrategy<Statement>();
strategy.setType(Statement.class);
strategy.setColumnMapping(columns);
filename = env.getProperty("globys.statement.cdf.path")+"-"+processCount+".dat";
File cdf = new File(filename);
if(cdf.exists()){
writer = Files.newBufferedWriter(Paths.get(filename), StandardCharsets.UTF_8,StandardOpenOption.APPEND);
}else{
writer = Files.newBufferedWriter(Paths.get(filename), StandardCharsets.UTF_8,StandardOpenOption.CREATE_NEW);
}
beanToCsv = new StatefulBeanToCsvBuilder<Statement>(writer).withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
.withMappingStrategy(strategy).withSeparator(',').build();
}
#Override
public void write(List<? extends BaseDTO> items) throws Exception {
List<Statement> settlementList = new ArrayList<Statement>();
for (int i = 0; i < items.size(); i++) {
BaseDTO baseDTO = items.get(i);
settlementList.addAll(baseDTO.getStatementList());
}
beanToCsv.write(settlementList);
writer.flush();
}
#PreDestroy
#Override
public void close() throws IOException {
writer.close();
}
}
Since you are using PassThroughLineAggregator which does item.toString() for writing the object, overriding the toString() function of classes extending Fixing.java should fix it.
1/ How can I write just the data so that the values are comma separated and where it is null, it is not written.
You need to provide a custom LineAggregator that filters out null fields.
2/ Secondly, I am having an issue where only when I exit spring boot application, I am able to see the file get created
This is probably because you are calling this.writer.open in the write method which is not correct. You need to make your item writer implement ItemStream and call this.writer.open and this this.writer.close respectively in ItemStream#open and ItemStream#close

javax.script.ScriptEngine Console extension or library?

With this I have added the console.log functionality to the javax.script.ScriptEngine.
public class Console {
public void log(String text){
System.out.println("console: " + text);
}
}
private static ScriptEngine getJavaScriptEngine(){
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
Console console = new Console();
engine.put("console", console);
return engine;
}
As console and alert etc. are not part of the implementation. After a lot of searching I only found here and eleswhere only the same statement but wondering if there is not a library which does this right ?
I had a similar solution, but found that it would not format objects / arrays. Nor that it would handle multiple arguments (e.g. console.log('this is the answer:', 42)).
To work around this, I had to polyfill console. It doesn't support all of its functions, but it will do for the purpose of logging. To format objects and arrays as JSON, it appeared that the JSON object wasn't available too - so had to polyfill that object too.
There is probably a prettier solution to this, but this setup got me going:
Polyfill the JSON object, take the script mentioned here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON#Polyfill. Rhino doesn't know about window, so replace:
if (!window.JSON) {
window.JSON = {
// ... rest of code ...
};
}
by:
// if (!window.JSON) {
// window.
JSON = {
// ... rest of code ...
};
// }
Save it under src/main/resources/scripts/json.js.
Then, for the console object, put the following content in src/main/resources/scripts/console.js:
console = {
_format: function(values) {
var msg = [];
for (var i=0;i<values.length;i++)
msg.push(JSON.stringify(values[i]));
return msg.join(', ');
},
log: function() {
log.fine(console._format(arguments));
},
info: function() {
log.info(console._format(arguments));
},
warn: function() {
log.warning(console._format(arguments));
}
};
Note that this console implementation uses a log global variable. In my case, this is a JUL Logger instance, but with a little bit of creativity it can be changed to use another logging framework (e.g. SLF4j).
Finally, to put this all together in Java code, it can be done like this:
public class ConsoleTest {
public static void main(String... args) throws ScriptException, IOException {
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("JavaScript");
engine.put("log", Logger.getLogger("script"));
run(engine, "/scripts/json.js");
run(engine, "/scripts/console.js");
engine.eval("console.log('string value');");
engine.eval("console.warn(['array','value']);");
engine.eval("console.info({a:1,b:'two'});");
}
private void run(ScriptEngine engine, String resourceName) throws ScriptException, IOException {
InputStream in = getClass().getResourceAsStream(resourceName);
Reader reader = new InputStreamReader(in, Charset.forName("UTF-8"));
engine.eval(reader);
reader.close();
}
}

Accessing Parent node in Child in ANTLR 4.4

Am new to ANTLR framework. I am working with parsing a Java file. Using ANTLR I am generating the JavaLexer, JavaParser, JavaListener, JavaBaseListener uning org.antlr.v4.Tool
Here I have an issue. I create a class that overrides the required methods of JavaBaseListener
Here is the code:
JavaMetaDataReader.java
public class JavaMetaDataReader extends JavaBaseListener{
#Override
public void enterAnnotation(AnnotationContext ctx)
{
System.out.println(ctx.getText());
}
#Override
public void enterAnnotationName(AnnotationNameContext ctx)
{
System.out.println(ctx.getText());
}
#Override
public void enterElementValuePairs(ElementValuePairsContext ctx)
{
System.out.println(ctx.getText());
System.out.println("Parent: "+ctx.getParent().getText());
}
}
JavaReader.Java //Contains the main method.
public class JavaReader {
public static void main(String[] args) {
File fileTobeRead = new File("./src/main/java/sample/HelloWord.java");
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(fileTobeRead);
ANTLRInputStream input = new ANTLRInputStream(fileInputStream);
JavaLexer lexer = new JavaLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
JavaParser parser = new JavaParser(tokens);
ParseTree tree = parser.compilationUnit(); // parse
ParseTreeWalker walker = new ParseTreeWalker(); // create standard walker
JavaMetaDataReader javaMetaDataReader = new JavaMetaDataReader();
walker.walk(javaMetaDataReader, tree); // initiate walk of tree with listener
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
HelloWorld.java
public class HelloWord {
#SuppressWarnings(value = "Helloo")
private void helloWorld() {
// TODO Auto-generated method stub
}
}
Here HelloWorld.java is the file that needs to be parsed.
Here am trying to access the annotations.
The enterAnnotation prints: #SuppressWarnings(value="Helloo")
The enterAnnotationName prints: SuppressWarnings
The first statement in enterElementValuePair prints: value="Helloo"
The second statement in enterElementValuePair prints: Parent: #SuppressWarnings(value="Helloo")
But i need it to print SuppressWarnings (the valure printed by enterAnnotationName
I don't know where I went wrong. I need to access the annotationName inside enterElementValuePairs.
What should I do?
Kindly help me as I am a beginner.
You didn't include a copy of the grammar, so I can only guess about the relation between your rules. It seems the parent of elementValuePairs is annotation, not annotationName. You need to first access the parent (AnnotationContext), and then call AnnotationContext.annotationName() to get the previous sibling of the ElementValuePairsContext.
If you are using the unofficial "optimized" release of the Java target, you can add the following annotation to your enterElementValuePairs method to ensure that a compiler error is reported if you later use the elementValuePairs rule in a way which would break this. Note that this requires the #version{} action to be used when the grammar is modified, as described in the link below.
#RuleDependencies({
#RuleDependency(recognizer=JavaParser.class, rule=JavaParser.RULE_elementValuePairs,
version=0, dependents={Dependents.PARENTS, Dependents.DESCENDANTS}),
#RuleDependency(recognizer=JavaParser.class, rule=JavaParser.RULE_annotation,
version=0, dependents=Dependents.SELF),
#RuleDependency(recognizer=JavaParser.class, rule=JavaParser.RULE_annotationName,
version=0, dependents=Dependents.DESCENDANTS)
})
More information about using rule dependencies is available here:
https://github.com/sharwell/antlr4/wiki/Rule-Versioning

Turn off date comment in properties file [duplicate]

Is it possible to force Properties not to add the date comment in front? I mean something like the first line here:
#Thu May 26 09:43:52 CEST 2011
main=pkg.ClientMain
args=myargs
I would like to get rid of it altogether. I need my config files to be diff-identical unless there is a meaningful change.
Guess not. This timestamp is printed in private method on Properties and there is no property to control that behaviour.
Only idea that comes to my mind: subclass Properties, overwrite store and copy/paste the content of the store0 method so that the date comment will not be printed.
Or - provide a custom BufferedWriter that prints all but the first line (which will fail if you add real comments, because custom comments are printed before the timestamp...)
Given the source code or Properties, no, it's not possible. BTW, since Properties is in fact a hash table and since its keys are thus not sorted, you can't rely on the properties to be always in the same order anyway.
I would use a custom algorithm to store the properties if I had this requirement. Use the source code of Properties as a starter.
Based on https://stackoverflow.com/a/6184414/242042 here is the implementation I have written that strips out the first line and sorts the keys.
public class CleanProperties extends Properties {
private static class StripFirstLineStream extends FilterOutputStream {
private boolean firstlineseen = false;
public StripFirstLineStream(final OutputStream out) {
super(out);
}
#Override
public void write(final int b) throws IOException {
if (firstlineseen) {
super.write(b);
} else if (b == '\n') {
firstlineseen = true;
}
}
}
private static final long serialVersionUID = 7567765340218227372L;
#Override
public synchronized Enumeration<Object> keys() {
return Collections.enumeration(new TreeSet<>(super.keySet()));
}
#Override
public void store(final OutputStream out, final String comments) throws IOException {
super.store(new StripFirstLineStream(out), null);
}
}
Cleaning looks like this
final Properties props = new CleanProperties();
try (final Reader inStream = Files.newBufferedReader(file, Charset.forName("ISO-8859-1"))) {
props.load(inStream);
} catch (final MalformedInputException mie) {
throw new IOException("Malformed on " + file, mie);
}
if (props.isEmpty()) {
Files.delete(file);
return;
}
try (final OutputStream os = Files.newOutputStream(file)) {
props.store(os, "");
}
if you try to modify in the give xxx.conf file it will be useful.
The write method used to skip the First line (#Thu May 26 09:43:52 CEST 2011) in the store method. The write method run till the end of the first line. after it will run normally.
public class CleanProperties extends Properties {
private static class StripFirstLineStream extends FilterOutputStream {
private boolean firstlineseen = false;
public StripFirstLineStream(final OutputStream out) {
super(out);
}
#Override
public void write(final int b) throws IOException {
if (firstlineseen) {
super.write(b);
} else if (b == '\n') {
// Used to go to next line if did use this line
// you will get the continues output from the give file
super.write('\n');
firstlineseen = true;
}
}
}
private static final long serialVersionUID = 7567765340218227372L;
#Override
public synchronized Enumeration<java.lang.Object> keys() {
return Collections.enumeration(new TreeSet<>(super.keySet()));
}
#Override
public void store(final OutputStream out, final String comments)
throws IOException {
super.store(new StripFirstLineStream(out), null);
}
}
Can you not just flag up in your application somewhere when a meaningful configuration change takes place and only write the file if that is set?
You might want to look into Commons Configuration which has a bit more flexibility when it comes to writing and reading things like properties files. In particular, it has methods which attempt to write the exact same properties file (including spacing, comments etc) as the existing properties file.
You can handle this question by following this Stack Overflow post to retain order:
Write in a standard order:
How can I write Java properties in a defined order?
Then write the properties to a string and remove the comments as needed. Finally write to a file.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
properties.store(baos,null);
String propertiesData = baos.toString(StandardCharsets.UTF_8.name());
propertiesData = propertiesData.replaceAll("^#.*(\r|\n)+",""); // remove all comments
FileUtils.writeStringToFile(fileTarget,propertiesData,StandardCharsets.UTF_8);
// you may want to validate the file is readable by reloading and doing tests to validate the expected number of keys matches
InputStream is = new FileInputStream(fileTarget);
Properties testResult = new Properties();
testResult.load(is);

Is it possible to create an URL pointing to an in-memory object?

I'm trying to extend my library for integrating Swing and JPA by making JPA config as automatic (and portable) as can be done, and it means programmatically adding <class> elements. (I know it can be done via Hibernate's AnnotationConfiguration or EclipseLInk's ServerSession, but - portability). I'd also like to avoid using Spring just for this single purpose.
I can create a persistence.xml on the fly, and fill it with <class> elements from specified packages (via the Reflections library). The problem starts when I try to feed this persistence.xml to a JPA provider. The only way I can think of is setting up a URLClassLoader, but I can't think of a way what wouldn't make me write the file to the disk somewhere first, for sole ability to obtain a valid URL. Setting up a socket for serving the file via an URL(localhost:xxxx) seems... I don't know, evil?
Does anyone have an idea how I could solve this problem? I know it sounds like a lot of work to avoid using one library, but I'd just like to know if it can be done.
EDIT (a try at being more clear):
Dynamically generated XML is kept in a String object. I don't know how to make it available to a persistence provider. Also, I want to avoid writing the file to disk.
For purpose of my problem, a persistence provider is just a class which scans the classpath for META-INF/persistence.xml. Some implementations can be made to accept dynamic creation of XML, but there is no common interface (especially for a crucial part of the file, the <class> tags).
My idea is to set up a custom ClassLoader - if you have any other I'd be grateful, I'm not set on this one.
The only easily extendable/configurable one I could find was a URLClassLoader. It works on URL objects, and I don't know if I can create one without actually writing XML to disk first.
That's how I'm setting things up, but it's working by writing the persistenceXmlFile = new File("META-INF/persistence.xml") to disk:
Thread.currentThread().setContextClassLoader(
new URLResourceClassLoader(
new URL[] { persistenceXmlFile.toURI().toURL() },
Thread.currentThread().getContextClassLoader()
)
);
URLResourceClassLoader is URLCLassLoader's subclass, which allows for looking up resources as well as classes, by overriding public Enumeration<URL> findResources(String name).
Maybe a bit late (after 4 years), but for others that are looking for a similar solution, you may be able to use the URL factory I created:
public class InMemoryURLFactory {
public static void main(String... args) throws Exception {
URL url = InMemoryURLFactory.getInstance().build("/this/is/a/test.txt", "This is a test!");
byte[] data = IOUtils.toByteArray(url.openConnection().getInputStream());
// Prints out: This is a test!
System.out.println(new String(data));
}
private final Map<URL, byte[]> contents = new WeakHashMap<>();
private final URLStreamHandler handler = new InMemoryStreamHandler();
private static InMemoryURLFactory instance = null;
public static synchronized InMemoryURLFactory getInstance() {
if(instance == null)
instance = new InMemoryURLFactory();
return instance;
}
private InMemoryURLFactory() {
}
public URL build(String path, String data) {
try {
return build(path, data.getBytes("UTF-8"));
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException(ex);
}
}
public URL build(String path, byte[] data) {
try {
URL url = new URL("memory", "", -1, path, handler);
contents.put(url, data);
return url;
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
private class InMemoryStreamHandler extends URLStreamHandler {
#Override
protected URLConnection openConnection(URL u) throws IOException {
if(!u.getProtocol().equals("memory")) {
throw new IOException("Cannot handle protocol: " + u.getProtocol());
}
return new URLConnection(u) {
private byte[] data = null;
#Override
public void connect() throws IOException {
initDataIfNeeded();
checkDataAvailability();
// Protected field from superclass
connected = true;
}
#Override
public long getContentLengthLong() {
initDataIfNeeded();
if(data == null)
return 0;
return data.length;
}
#Override
public InputStream getInputStream() throws IOException {
initDataIfNeeded();
checkDataAvailability();
return new ByteArrayInputStream(data);
}
private void initDataIfNeeded() {
if(data == null)
data = contents.get(u);
}
private void checkDataAvailability() throws IOException {
if(data == null)
throw new IOException("In-memory data cannot be found for: " + u.getPath());
}
};
}
}
}
We can use the Jimfs google library for that.
First, we need to add the maven dependency to our project:
<dependency>
<groupId>com.google.jimfs</groupId>
<artifactId>jimfs</artifactId>
<version>1.2</version>
</dependency>
After that, we need to configure our filesystem behavior, and write our String content to the in-memory file, like this:
public static final String INPUT =
"\n"
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<note>\n"
+ " <to>Tove</to>\n"
+ " <from>Jani</from>\n"
+ " <heading>Reminder</heading>\n"
+ " <body>Don't forget me this weekend!</body>\n"
+ "</note>";
#Test
void usingJIMFS() throws IOException {
try (var fs = Jimfs.newFileSystem(Configuration.unix())) {
var path = fs.getPath(UUID.randomUUID().toString());
Files.writeString(path, INPUT);
var url = path.toUri().toURL();
assertThat(url.getProtocol()).isEqualTo("jimfs");
assertThat(Resources.asCharSource(url, UTF_8).read()).isEqualTo(INPUT);
}
}
We can find more examples in the official repository.
If we look inside the jimfs source code we will find the implementation is similar to #NSV answer.

Categories

Resources