I'm using Groovy's StreamingMarkupBuilder to generate XML dynamically based on the results of a few SQL queries. I'd like to call a method from inside of the closure but the markup builder tries to create an XML node using the method name.
Here's an example of what I'm trying to do:
Map generateMapFromRow(GroovyRowResult row) {
def map = [:]
def meta = row.getMetaData()
// Dynamically generate the keys and values
(1..meta.getColumnCount()).each { column -> map[meta.getColumnName(column)] = row[column-1] }
return map
}
def sql = Sql.newInstance(db.url, db.user, db.password, db.driver)
def builder = new StreamingMarkupBuilder()
def studentsImport = {
students {
sql.eachRow('select first_name, middle_name, last_name from students') { row ->
def map = generateMapFromRow(row) // Here is the problem line
student(map)
}
}
}
println builder.bind(studentsImport).toString()
This will generate XML similar to the following:
<students>
<generateMapFromRow>
[first_name:Ima, middle_name:Good, last_name:Student]
</generateMapFromRow>
<student/>
<generateMapFromRow>
[first_name:Ima, middle_name:Bad, last_name:Student]
</generateMapFromRow>
<student/>
</students>
I've tried moving the method out to a class and calling to statically on the class, which doesn't work also.
Due to the nature of how StreamingMarkupBuilder works, I'm afraid that it isn't actually possible to do this, but I'm hoping that it is.
I may loose smth during example simplification, but such code will work.
In your example students is a closure call, so it may mess smth inside.
def builder = new groovy.xml.StreamingMarkupBuilder()
def generateMapFromRow = { ["$it": it] }
builder.bind {
10.times {
def map = generateMapFromRow(it) // Now closure is escaped, there is local variable with such name.
student(map)
}
}
As said here: http://groovy.codehaus.org/Using+MarkupBuilder+for+Agile+XML+creation
Things to be careful about when using markup builders is not to overlap variables you currently have in scope. The following is a good example
import groovy.xml.MarkupBuilder
def book = "MyBook"
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.shelf() {
book(name:"Fight Club") { // Will produce error.
}
}
println writer.toString()
Builder's work similar to MethodMissing captors, ans if there is local variable in scope, no node will be produced.
Related
I'm sorry this question header is not 100% correct. Because of that, I'll explain my scenario here.
I created a function to merge 4 data sets into one return format. Because that's the format front-end side needed. So this is working fine now.
public ReturnFormat makeThribleLineChart(List<NameCountModel> totalCount, List<NameCountModel>,p1Count, List<NameCountModel> p2Count, List<NameCountModel> average) {
ReturnFormat returnFormat = new ReturnFormat(null,null);
try {
String[] totalData = new String[totalCount.size()];
String[] p1Data = new String[p1Count.size()];
String[] p2Data = new String[p2Count.size()];
String[] averageData = new String[p2Count.size()];
String[] lableList = new String[totalCount.size()];
for (int x = 0; x < totalCount.size(); x++) {
totalData[x] = totalCount.get(x).getCount();
p1Data[x] = p1Count.get(x).getCount();
p2Data[x] = p2Count.get(x).getCount();
averageData[x] = average.get(x).getCount();
lableList[x] = totalCount.get(x).getName();
}
FormatHelper<String[]> totalFormatHelper= new FormatHelper<String[]>();
totalFormatHelper.setData(totalData);
totalFormatHelper.setType("line");
totalFormatHelper.setLabel("Uudet");
totalFormatHelper.setyAxisID("y-axis-1");
FormatHelper<String[]> p1FormatHelper= new FormatHelper<String[]>();
p1FormatHelper.setData(p1Data);
p1FormatHelper.setType("line");
p1FormatHelper.setLabel("P1 päivystykseen heti");
FormatHelper<String[]> p2FormatHelper= new FormatHelper<String[]>();
p2FormatHelper.setData(p2Data);
p2FormatHelper.setType("line");
p2FormatHelper.setLabel("P2 päivystykseen muttei yöllä");
FormatHelper<String[]> averageFormatHelper= new FormatHelper<String[]>();
averageFormatHelper.setData(averageData);
averageFormatHelper.setType("line");
averageFormatHelper.setLabel("Jonotusaika keskiarvo");
averageFormatHelper.setyAxisID("y-axis-2");
List<FormatHelper<String[]>> formatHelpObj = new ArrayList<FormatHelper<String[]>>();
formatHelpObj.add(totalFormatHelper);
formatHelpObj.add(p1FormatHelper);
formatHelpObj.add(p2FormatHelper);
formatHelpObj.add(averageFormatHelper);
returnFormat.setData(formatHelpObj);
returnFormat.setLabels(lableList);
returnFormat.setMessage(Messages.Success);
returnFormat.setStatus(ReturnFormat.Status.SUCCESS);
} catch (Exception e) {
returnFormat.setData(null);
returnFormat.setMessage(Messages.InternalServerError);
returnFormat.setStatus(ReturnFormat.Status.ERROR);
}
return returnFormat;
}
so, as you can see here, all the formatting is hardcoded. So my question is how to automate this code for list count. Let's assume next time I have to create chart formatting for five datasets. So I have to create another function to it. That's the thing I want to reduce. So I hope you can understand my question.
Thank you.
You're trying to solve the more general problem of composing a result object (in this case ReturnFormat) based on dynamic information. In addition, there's some metadata being setup along with each dataset - the type, label, etc. In the example that you've posted, you've hardcoded the relationship between a dataset and this metadata, but you'd need some way to establish this relationship for data dynamically if you have a variable number of parameters here.
Therefore, you have a couple of options:
Make makeThribleLineChart a varargs method to accept a variable number of parameters representing your data. Now you have the problem of associating metadata with your parameters - best option is probably to wrap the data and metadata together in some new object that is provided as each param of makeThribleLineChart.
So you'll end up with a signature that looks a bit like ReturnFormat makeThribleLineChart(DataMetadataWrapper... allDatasets), where DataMetadataWrapper contains everything required to build one FormatHelper instance.
Use a builder pattern, similar to the collection builders in guava, for example something like so:
class ThribbleLineChartBuilder {
List<FormatHelper<String[]>> formatHelpObj = new ArrayList<>();
ThribbleLineChartBuilder addDataSet(String describeType, String label, String yAxisId, List<NameCountModel> data) {
String[] dataArray = ... ; // build your array of data
FormatHelper<String[]> formatHelper = new FormatHelper<String[]>();
formatHelper.setData(dataArray);
formatHelper.setType(describeType);
... // set any other parameters that the FormatHelper requires here
formatHelpObj.add(formatHelper);
return this;
}
ReturnFormat build() {
ReturnFormat returnFormat = new ReturnFormat(null, null);
returnFormat.setData(this.formatHelpObj);
... // setup any other fields you need in ReturnFormat
return returnFormat;
}
}
// usage:
new ThribbleLineChartBuilder()
.addDataSet("line", "Uudet", "y-axis-1", totalCount)
.addDataSet("line", "P1 päivystykseen heti", null, p1Count)
... // setup your other data sources
.build()
According to the official documentation Update API - Upserts one can use scripted_upsert in order to handle update (for existing document) or insert (for new document) form within the script. The thing is they never show how the script should look to do that. The Java - Update API Doesn't have any information on the ScriptUpsert uses.
This is the code I'm using:
//My function to build and use the upsert
public void scriptedUpsert(String key, String parent, String scriptSource, Map<String, ? extends Object> parameters) {
Script script = new Script(scriptSource, ScriptType.INLINE, null, parameters);
UpdateRequest request = new UpdateRequest(index, type, key);
request.scriptedUpsert(true);
request.script(script);
if (parent != null) {
request.parent(parent);
}
this.bulkProcessor.add(request);
}
//A test call to validate the function
String scriptSource = "if (!ctx._source.hasProperty(\"numbers\")) {ctx._source.numbers=[]}";
Map<String, List<Integer>> parameters = new HashMap<>();
List<Integer> numbers = new LinkedList<>();
numbers.add(100);
parameters.put("numbers", numbers);
bulk.scriptedUpsert("testUser", null, scriptSource, parameters);
And I'm getting the following exception when "testUser" documents doesn't exists:
DocumentMissingException[[user][testUser]: document missing
How can I make the scriptUpsert work from the Java code?
This is how a scripted_upsert command should look like (and its script):
POST /sessions/session/1/_update
{
"scripted_upsert": true,
"script": {
"inline": "if (ctx.op == \"create\") ctx._source.numbers = newNumbers; else ctx._source.numbers += updatedNumbers",
"params": {
"newNumbers": [1,2,3],
"updatedNumbers": [55]
}
},
"upsert": {}
}
If you call the above command and the index doesn't exist, it will create it, together with the newNumbers values in the new documents. If you call again the exact same command the numbers values will become 1,2,3,55.
And in your case you are missing "upsert": {} part.
As Andrei suggested I was missing the upsert part, changing the function to:
public void scriptedUpsert(String key, String parent, String scriptSource, Map<String, ? extends Object> parameters) {
Script script = new Script(scriptSource, ScriptType.INLINE, null, parameters);
UpdateRequest request = new UpdateRequest(index, type, key);
request.scriptedUpsert(true);
request.script(script);
request.upsert("{}"); // <--- The change
if (parent != null) {
request.parent(parent);
}
this.bulkProcessor.add(request);
}
Fix it.
I'm writing Intellij IDEA plugin for my project, and I've faced a problem - I cannot get some ingo about method (PsiMethod) from my code.
First, I want to know is this method public.
And second, I want to get fully-qualified names of the parameter classes. Currently I'm doing it like this:
method.getReturnTypeNoResolve().getInternalCanonicalText()
But it doesn't provide full name (with package name) for the standard JVM classes like String and List.
UPDATE First problem solved with the following code:
PsiUtil.getAccessLevel(method.getModifierList()) == PsiUtil.ACCESS_LEVEL_PUBLIC
But I still cannot get fully qualified class name
UPDATE 2 Here is the full listing of my code:
Project currentProject = DataKeys.PROJECT.getData(e.getDataContext());
PsiClass abstractComponentClass = JavaPsiFacade.getInstance(currentProject).findClass("com.mjolnirr.lib.component.AbstractComponent", GlobalSearchScope.allScope(currentProject));
TreeClassChooser result = TreeClassChooserFactory
.getInstance(currentProject)
.createInheritanceClassChooser("Choose the class to generate manifest",
GlobalSearchScope.projectScope(currentProject),
abstractComponentClass,
false,
false,
null);
result.showDialog();
PsiClass classToGenerate = result.getSelected();
List<ManifestMethod> methods = new ArrayList<ManifestMethod>();
for (PsiMethod method : classToGenerate.getAllMethods()) {
// If this method is inherited from the Object class we don't need it
if (isComponentInitialize(method)) {
continue;
}
List<ManifestParameter> parameters = new ArrayList<ManifestParameter>();
for (PsiParameter param : method.getParameterList().getParameters()) {
parameters.add(new ManifestParameter(param.getType().getCanonicalText().replaceAll("\\<.*?\\>", "")));
}
if (method.getReturnType() != null) {
ManifestMethod manifestMethod = new ManifestMethod(method.getName(),
method.getReturnTypeNoResolve().getInternalCanonicalText().replaceAll("\\<.*?\\>", ""),
parameters);
if (!methods.contains(manifestMethod) && isPublic(method)) {
System.out.println("->" + method.getReturnType().getCanonicalText());
methods.add(manifestMethod);
}
}
}
Solved - my test IDEA instance has wrong Java SDK connected.
I create a groovy engine with the GroovyShell class.
Then I run a bunch of statements with the "evaluate" method.
Is there a way to catch the output of the engine so I can get "println" calls output?
Currently it goes to stdout although it is a swing application.
You can just assign your custom Writer (eg. StringWriter) to out property in the binding and pass it to the GroovyShell.
def binding = new Binding();
binding.setProperty("out", new YourWriter())
new GroovyShell(binding);
You can set a scriptBaseClass with a println method and you are free to operate on top of the value. Remember the user still can do System.out.println, but you can blacklist it, if needed.
import org.codehaus.groovy.control.CompilerConfiguration
def script = """
a = 10
println a
println "echo"
"""
abstract class Printer extends Script {
void println(obj) {
this.binding.printed << obj
}
}
def config = new CompilerConfiguration(scriptBaseClass: Printer.class.name)
def binding = new Binding([printed: []])
new GroovyShell(this.class.classLoader, binding, config).evaluate script
assert binding.variables.printed.contains( 10 )
assert binding.variables.printed.contains( "echo" )
I have a grails project with a class that I can delete no problem when doing it "manually" from the controller. I use the following code.
def delete = {
def projectInstance = Project.get( params.id )
def employee = projectInstance.employee
def projectarray = new ArrayList<Project>();
projectarray += employee.getProjects()
println("Size of projectarray is " + projectarray.size())
if(projectInstance) {
def rolearray = []
projectarray.remove(projectInstance)
def temp = new TreeSet<Project>();
temp += employee.getProjects()
temp.clear()
temp.addAll(projectarray)
employee.projects = temp
projectInstance.employer = null
projectInstance.delete(flush:true)
flash.message = "Project ${params.id} deleted"
redirect(action:"edit", controller: "employee", id: employee.id)
}
else {
flash.message = "Project not found with id ${params.id}"
redirect(action:list)
}
}
So that deletes a single instance fine.
Now i want to, from a different controller, remove ALL projects from an employee.
This is stored in the employee like so:
class Employee implements Comparable
{
static hasMany = [projects:Project]
static constraints =
{
}
static mapping = {
projects cascade:"all-delete-orphan", lazy:false
}
#XmlElementWrapper(name="projectslist")
SortedSet<Project> projects = new TreeSet<Project>(); // make a sortedSet?
}
So how would I now delete all projects from a particular employee instance?
I might be misunderstanding your question because I can't make sense of some of your code. It seems unnecessary. If your relationships are setup correctly (i.e. Project belongsTo Employee), this should be sufficient to delete a single project:
def delete = {
def projectInstance = Project.get( params.id )
projectInstance.delete(flush:true)
flash.message = "Project ${params.id} deleted"
redirect(action:"edit", controller: "employee", id: employee.id)
}
If this is a one-to-many, the next time you retrieve the employee the project will be gone. And this should work to delete all projects of an employee:
def delete = {
def employee = Employee.get( params.id )
employee.getProjects().clear()
employee.save(flash:true)
flash.message = "All projects of employee deleted."
redirect(action:"edit", controller: "employee", id: employee.id)
}
That assumes cascade:"all-delete-orphan". If that's not the case then you might need to also delete the instances and that might look something like this:
def delete = {
def employee = Employee.get( params.id )
// Make copy to avoid concurrent modification issues later
def copy = new TreeSet<Project>(employee.getProjects());
employee.getProjects().clear();
employee.save(flash:true)
copy.each{
$it.delete();
}
flash.message = "All projects of employee deleted."
redirect(action:"edit", controller: "employee", id: employee.id)
}
I'm not a groovy expert, so not sure if the copy is needed, or if you can just iterate on the collection directly. Seems like there is always a groovier way to do things. You might also want to check out the deleteFrom dynamic domain class method. That might be a more efficient grails approach depending on number of relationships to be deleted.
You could use the removeFrom* method that is generated by Grails when you declare the hasMany relationship - it's the equivalent of the addTo* methods:
def employee = Employee.get(params.id)
employee.projects.toList().each { employee.removeFromProjects(it) } // toList() prevents a ConcurrentModifactionException