I'm trying to write a large number of rows (~2 million) from a database to a CSV file using SuperCSV. I need to perform validation on each cell as it is written, and the built-in CellProcessors do very nicely. I want to capture all the exceptions that are thrown by the CellProcessors so that I can go back to the source data and make changes.
The problem is that when there are multiple errors in a single row (e.g. The first value is out of range, the second value is null but shouldn't be), only the first CellProcessor will execute, and so I'll only see one of the errors. I want to process the whole file in a single pass, and have a complete set of exceptions at the end of it.
This is the kind of approach I'm trying:
for (Row row : rows) {
try {
csvBeanWriter.write(row, HEADER_MAPPINGS, CELL_PROCESSORS);
} catch (SuperCsvCellProcessorException e) {
log(e);
}
}
How can I achieve this? Thanks!
EDIT: Here is the code I wrote that's similar to Hound Dog's, in case it helps anyone:
import java.util.List;
import org.supercsv.cellprocessor.CellProcessorAdaptor;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.exception.SuperCsvCellProcessorException;
import org.supercsv.util.CsvContext;
public class ExceptionCapturingCellProcessor extends CellProcessorAdaptor {
private final List<Exception> exceptions;
private final CellProcessor current;
public ExceptionCapturingCellProcessor(CellProcessor current, CellProcessor next, List<Exception> exceptions) {
super(next);
this.exceptions = exceptions;
this.current = current;
}
#Override
public Object execute(Object value, CsvContext context) {
// Check input is not null
try {
validateInputNotNull(value, context);
} catch (SuperCsvCellProcessorException e) {
exceptions.add(e);
}
// Execute wrapped CellProcessor
try {
current.execute(value, context);
} catch (SuperCsvCellProcessorException e) {
exceptions.add(e);
}
return next.execute(value, context);
}
}
I'd recommend writing a custom CellProcessor to achieve this. The following processor can be placed at the start of each CellProcessor chain - it will simply delegate to the processor chained after it, and will suppress any cell processing exceptions.
package example;
import java.util.ArrayList;
import java.util.List;
import org.supercsv.cellprocessor.CellProcessorAdaptor;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.exception.SuperCsvCellProcessorException;
import org.supercsv.util.CsvContext;
public class SuppressException extends CellProcessorAdaptor {
public static List<SuperCsvCellProcessorException> SUPPRESSED_EXCEPTIONS =
new ArrayList<SuperCsvCellProcessorException>();
public SuppressException(CellProcessor next) {
super(next);
}
public Object execute(Object value, CsvContext context) {
try {
// attempt to execute the next processor
return next.execute(value, context);
} catch (SuperCsvCellProcessorException e) {
// save the exception
SUPPRESSED_EXCEPTIONS.add(e);
// and suppress it (null is written as "")
return null;
}
}
}
And here it is in action:
package example;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import org.supercsv.cellprocessor.constraint.NotNull;
import org.supercsv.cellprocessor.constraint.StrMinMax;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.exception.SuperCsvCellProcessorException;
import org.supercsv.io.CsvBeanWriter;
import org.supercsv.io.ICsvBeanWriter;
import org.supercsv.prefs.CsvPreference;
public class TestSuppressExceptions {
private static final CellProcessor[] PROCESSORS = {
new SuppressException(new StrMinMax(0, 4)),
new SuppressException(new NotNull()) };
private static final String[] HEADER = { "name", "age" };
public static void main(String[] args) throws Exception {
final StringWriter stringWriter = new StringWriter();
ICsvBeanWriter beanWriter = null;
try {
beanWriter = new CsvBeanWriter(stringWriter,
CsvPreference.STANDARD_PREFERENCE);
beanWriter.writeHeader(HEADER);
// set up the data
Person valid = new Person("Rick", 43);
Person nullAge = new Person("Lori", null);
Person totallyInvalid = new Person("Shane", null);
Person valid2 = new Person("Carl", 12);
List<Person> people = Arrays.asList(valid, nullAge, totallyInvalid,
valid2);
for (Person person : people) {
beanWriter.write(person, HEADER, PROCESSORS);
if (!SuppressException.SUPPRESSED_EXCEPTIONS.isEmpty()) {
System.out.println("Suppressed exceptions for row "
+ beanWriter.getRowNumber() + ":");
for (SuperCsvCellProcessorException e :
SuppressException.SUPPRESSED_EXCEPTIONS) {
System.out.println(e);
}
// clear ready for next row
SuppressException.SUPPRESSED_EXCEPTIONS.clear();
}
}
} finally {
beanWriter.close();
}
// CSV will have empty columns for invalid data
System.out.println(stringWriter);
}
}
Here's the suppressed exceptions output (row 4 has two exceptions, one for each column):
Suppressed exceptions for row 3:
org.supercsv.exception.SuperCsvConstraintViolationException: null value
encountered processor=org.supercsv.cellprocessor.constraint.NotNull
context={lineNo=3, rowNo=3, columnNo=2, rowSource=[Lori, null]}
Suppressed exceptions for row 4:
org.supercsv.exception.SuperCsvConstraintViolationException: the length (5)
of value 'Shane' does not lie between the min (0) and max (4) values (inclusive)
processor=org.supercsv.cellprocessor.constraint.StrMinMax
context={lineNo=4, rowNo=4, columnNo=2, rowSource=[Shane, null]}
org.supercsv.exception.SuperCsvConstraintViolationException: null value
encountered processor=org.supercsv.cellprocessor.constraint.NotNull
context={lineNo=4, rowNo=4, columnNo=2, rowSource=[Shane, null]}
And the CSV output
name,age
Rick,43
Lori,
,
Carl,12
Notice how the invalid values were written as "" because the SuppressException processor returned null for those values (not that you'd use the CSV output anyway, as it's not valid!).
Related
Intellij keeps saying Undefined Step when running my feature file. However, I have copied the steps and put them into another package and added that package name in the "glue" parameter for Edit configurations. Still not working.
I've added the feature file and it doesn't show any missing step references. I am adding a screenshot. I have added all the steps in another package.
Please see below
The code for CountriesSteps is as follows
package Steps;
import Fixtures.Countries;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import org.junit.Assert;
public class CountriesSteps {
Countries countriesclass = new Countries();
#Given("^I generate a restful request for countries$")
public void iGenerateARestfulRequestForCountries() throws Throwable {
countriesclass.GetCountries();
}
#When("^I receive a successful country response (.*)$")
public void iReceiveASuccessfulCountryResponseResponse(int code) throws Throwable {
Assert.assertEquals(code, countriesclass.getsCode());
}
#When("^I receive a successful country response$")
public void iReceiveASuccessfulCountryResponse() throws Throwable {
Assert.assertEquals(200, countriesclass.getsCode());
}
#Then("^the api country response returns (.*)$")
public void theApiCountryResponseReturnsCountries(int countries) throws Throwable {
Assert.assertEquals(countries, countriesclass.getCount());
}
#Then("^the api country response returns (.*),(.*),(.*),(.*),(.*)$")
public void theApiCountryResponseReturnsCountriesNameCapitalPopulation(int countries, int index, String name, String capital, int population) throws Throwable {
Assert.assertEquals(countries, countriesclass.getCount());
}
#Then("^the api country response for Index (.*) returns (.*),(.*),(.*)$")
public void theApiCountryResponseForIndexIndexReturnsNameCapitalPopulation(int index, String name, String capital, int population) throws Throwable {
//Validate a few values from response
Assert.assertEquals(name, countriesclass.getcList().get(index).name);
Assert.assertEquals(capital, countriesclass.getcList().get(index).capital);
Assert.assertEquals(population, countriesclass.getcList().get(index).population);
}
}
And code for Countries is
package Fixtures;
import Models.CountriesData;
import com.jayway.restassured.response.Response;
import gherkin.deps.com.google.gson.Gson;
import gherkin.deps.com.google.gson.reflect.TypeToken;
import org.json.JSONArray;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import static com.jayway.restassured.RestAssured.get;
public class Countries {
private static String url;
private static int count;
private static int sCode;
private static List<CountriesData> cList;
public void GetCountries() throws Exception
{
try {
url = "http://restcountries.eu/rest/v1/all";
// make get request to fetch json response from restcountries
Response resp = get(url);
//Fetching response in JSON as a string then convert to JSON Array
JSONArray jsonResponse = new JSONArray(resp.asString());
count = jsonResponse.length(); // how many items in the array
sCode = resp.statusCode(); // status code of 200
//create new arraylist to match CountriesData
List<CountriesData> cDataList = new ArrayList<CountriesData>();
Gson gson = new Gson();
Type listType = new TypeToken<List<CountriesData>>() {}.getType();
cDataList = gson.fromJson(jsonResponse.toString(), listType);
cList = cDataList;
}
catch (Exception e)
{
System.out.println("There is an error connecting to the API: " + e);
e.getStackTrace();
}
}
//getters to return ('get) the values
public int getsCode() {
return sCode;
}
public int getCount() {
return count;
}
public List<CountriesData> getcList() {
return cList;
}
}
When creating a new step definition file, IntelliJ by default proposes file path of \IdeaProjects\RestAPI\src\main\resources\stepmethods.
Your step definition folder is \IdeaProjects\RestAPI\src\test\java\Steps. Make sure cucumber isn't looking in the wrong place.
In my application I have a method which I cant execute without main method. It only runs inside the main method. When I call that method inside my servlet class. It show an exception
My class with Main Method
package com.books.servlet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.HashSet;
import java.util.Set;
import opennlp.tools.cmdline.parser.ParserTool;
import opennlp.tools.parser.Parse;
import opennlp.tools.parser.Parser;
import opennlp.tools.parser.ParserFactory;
import opennlp.tools.parser.ParserModel;
public class ParserTest {
// download
public void download(String url, File destination) throws IOException, Exception {
URL website = new URL(url);
ReadableByteChannel rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
public static Set<String> nounPhrases = new HashSet<>();
private static String line = "The Moon is a barren, rocky world ";
public void getNounPhrases(Parse p) {
if (p.getType().equals("NN") || p.getType().equals("NNS") || p.getType().equals("NNP")
|| p.getType().equals("NNPS")) {
nounPhrases.add(p.getCoveredText());
}
for (Parse child : p.getChildren()) {
getNounPhrases(child);
}
}
public void parserAction() throws Exception {
// InputStream is = new FileInputStream("en-parser-chunking.bin");
File modelFile = new File("en-parser-chunking.bin");
if (!modelFile.exists()) {
System.out.println("Downloading model.");
download("https://drive.google.com/uc?export=download&id=0B4uQtYVPbChrY2ZIWmpRQ1FSVVk", modelFile);
}
ParserModel model = new ParserModel(modelFile);
Parser parser = ParserFactory.create(model);
Parse topParses[] = ParserTool.parseLine(line, parser, 1);
for (Parse p : topParses) {
// p.show();
getNounPhrases(p);
}
}
public static void main(String[] args) throws Exception {
new ParserTest().parserAction();
System.out.println("List of Noun Parse : " + nounPhrases);
}
}
It gives me below output
List of Noun Parse : [barren,, world, Moon]
Then I commented the main method and. Called the ParserAction() method in my servlet class
if (name.equals("bkDescription")) {
bookDes = value;
try {
new ParserTest().parserAction();
System.out.println("Nouns Are"+ParserTest.nounPhrases);
} catch (Exception e) {
}
It gives me the below exceptions
And below error in my Browser
Why is this happening ? I can run this with main method. But when I remove main method and called in my servlet. it gives an exception. Is there any way to fix this issue ?
NOTE - I have read below instructions in OpenNLP documentation , but I have no clear idea about it. Please help me to fix his issue.
Unlike the other components to instantiate the Parser a factory method
should be used instead of creating the Parser via the new operator.
The parser model is either trained for the chunking parser or the tree
insert parser the parser implementation must be chosen correctly. The
factory method will read a type parameter from the model and create an
instance of the corresponding parser implementation.
Either create an object of ParserTest class or remove new keyword in this line new ParserTest().parserAction();
I am working on a requirement where I need to parse CSV record fields against multiple validations. I am using supercsv which has support for field level processors to validate data.
My requirement is to validate each record/row field against multiple validations and save them to the database with success/failure status. for failure records I have to display all the failed validations using some codes.
Super CSV is working file but it is checking only first validation for a filed and if it is failed , ignoring second validation for the same field.Please look at below code and help me on this.
package com.demo.supercsv;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import org.supercsv.cellprocessor.Optional;
import org.supercsv.cellprocessor.constraint.NotNull;
import org.supercsv.cellprocessor.constraint.StrMinMax;
import org.supercsv.cellprocessor.constraint.StrRegEx;
import org.supercsv.cellprocessor.constraint.UniqueHashCode;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.exception.SuperCsvCellProcessorException;
import org.supercsv.io.CsvBeanReader;
import org.supercsv.io.CsvBeanWriter;
import org.supercsv.io.ICsvBeanReader;
import org.supercsv.io.ICsvBeanWriter;
import org.supercsv.prefs.CsvPreference;
public class ParserDemo {
public static void main(String[] args) throws IOException {
List<Employee> emps = readCSVToBean();
System.out.println(emps);
System.out.println("******");
writeCSVData(emps);
}
private static void writeCSVData(List<Employee> emps) throws IOException {
ICsvBeanWriter beanWriter = null;
StringWriter writer = new StringWriter();
try{
beanWriter = new CsvBeanWriter(writer, CsvPreference.STANDARD_PREFERENCE);
final String[] header = new String[]{"id","name","role","salary"};
final CellProcessor[] processors = getProcessors();
// write the header
beanWriter.writeHeader(header);
//write the beans data
for(Employee emp : emps){
beanWriter.write(emp, header, processors);
}
}finally{
if( beanWriter != null ) {
beanWriter.close();
}
}
System.out.println("CSV Data\n"+writer.toString());
}
private static List<Employee> readCSVToBean() throws IOException {
ICsvBeanReader beanReader = null;
List<Employee> emps = new ArrayList<Employee>();
try {
beanReader = new CsvBeanReader(new FileReader("src/employees.csv"),
CsvPreference.STANDARD_PREFERENCE);
// the name mapping provide the basis for bean setters
final String[] nameMapping = new String[]{"id","name","role","salary"};
//just read the header, so that it don't get mapped to Employee object
final String[] header = beanReader.getHeader(true);
final CellProcessor[] processors = getProcessors();
Employee emp;
while ((emp = beanReader.read(Employee.class, nameMapping,
processors)) != null) {
emps.add(emp);
if (!CaptureExceptions.SUPPRESSED_EXCEPTIONS.isEmpty()) {
System.out.println("Suppressed exceptions for row "
+ beanReader.getRowNumber() + ":");
for (SuperCsvCellProcessorException e :
CaptureExceptions.SUPPRESSED_EXCEPTIONS) {
System.out.println(e);
}
// for processing next row clearing validation list
CaptureExceptions.SUPPRESSED_EXCEPTIONS.clear();
}
}
} finally {
if (beanReader != null) {
beanReader.close();
}
}
return emps;
}
private static CellProcessor[] getProcessors() {
final CellProcessor[] processors = new CellProcessor[] {
new CaptureExceptions(new NotNull(new StrRegEx("\\d+",new StrMinMax(0, 2)))),//id must be in digits and should not be more than two charecters
new CaptureExceptions(new Optional()),
new CaptureExceptions(new Optional()),
new CaptureExceptions(new NotNull()),
// Salary
};
return processors;
}
}
Exception Handler:
package com.demo.supercsv;
import java.util.ArrayList;
import java.util.List;
import org.supercsv.cellprocessor.CellProcessorAdaptor;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.exception.SuperCsvCellProcessorException;
import org.supercsv.util.CsvContext;
public class CaptureExceptions extends CellProcessorAdaptor {
public static List<SuperCsvCellProcessorException> SUPPRESSED_EXCEPTIONS =
new ArrayList<SuperCsvCellProcessorException>();
public CaptureExceptions(CellProcessor next) {
super(next);
}
public Object execute(Object value, CsvContext context) {
try {
return next.execute(value, context);
} catch (SuperCsvCellProcessorException e) {
// save the exception
SUPPRESSED_EXCEPTIONS.add(e);
if(value!=null)
return value.toString();
else
return "";
}
}
}
sample csv file
ID,Name,Role,Salary
a123,kiran,CEO,"5000USD"
2,Kumar,Manager,2000USD
3,David,developer,1000USD
when I run my program supercsv exception handler displaying this message for the ID value in the first row
Suppressed exceptions for row 2:
org.supercsv.exception.SuperCsvConstraintViolationException: 'a123' does not match the regular expression '\d+'
processor=org.supercsv.cellprocessor.constraint.StrRegEx
context={lineNo=2, rowNo=2, columnNo=1, rowSource=[a123, kiran, CEO, 5000USD]}
[com.demo.supercsv.Employee#23bf011e, com.demo.supercsv.Employee#50e26ae7, com.demo.supercsv.Employee#40d88d2d]
for field Id length should not be null and more than two and it should be neumeric...I have defined field processor like this.
new CaptureExceptions(new NotNull(new StrRegEx("\\d+",new StrMinMax(0, 2))))
but super csv ignoring second validation (maxlenght 2) if given input is not neumeric...if my input is 100 then its validating max lenght..but how to get two validations for wrong input.plese help me on this
SuperCSV cell processors will work in sequence. So, if it passes the previous constraint validation then it will check next one.
To achieve your goal, you need to write a custom CellProcessor, which will check whether the input is a number (digit) and length is between 0 to 2.
So, that both of those checks are done in a single step.
I'm trying to convert the below velocity macro into a velocity Java directive, as I need to add some bells and whistles around the rendering logic:
#macro(renderModules $modules)
#if($modules)
#foreach($module in $modules)
#if(${module.template})
#set($moduleData = $module.data)
#parse("${module.template}.vm")
#end
#end
#end
#end
My equivalent Java Directive:
import org.apache.velocity.context.InternalContextAdapter;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.directive.Directive;
import org.apache.velocity.runtime.parser.node.ASTBlock;
import org.apache.velocity.runtime.parser.node.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
public class RenderModulesDirective extends Directive {
private static final Logger LOGGER = LoggerFactory.getLogger(RenderModulesDirective.class);
#Override
public String getName() {
return "renderModules";
}
#Override
public int getType() {
return LINE;
}
#Override
public boolean render(InternalContextAdapter context, Writer writer, Node node) throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {
for(int i=0; i<node.jjtGetNumChildren(); i++) {
Node modulesNode = node.jjtGetChild(i);
if (modulesNode != null) {
if(!(modulesNode instanceof ASTBlock)) {
if(i == 0) {
// This should be the list of modules
List<Module> modules = (List<Module>) modulesNode.value(context);
if(modules != null) {
for (Module module : modules) {
context.put("moduleData", module.getData());
String templateName = module.getTemplate() + ".vm";
try {
// ??? How to parse the template here ???
} catch(Exception e) {
LOGGER.error("Encountered an error while rendering the Module {}", module, e);
}
}
break;
}
}
}
}
}
return true;
}
}
So, I'm stuck at the point where I need the Java equivalent of the #parse("<template_name>.vm") call. Is this the right approach? Would it help to instead extend from the Parse directive?
I believe
Template template = Velocity.getTemplate("path/to/template.vm");
template.merge(context, writer);
will accomplish what you're looking to do.
If you have access to RuntimeServices you could call createNewParser() and then call parse(Reader reader, String templateName) inside of the parser, the SimpleNode that comes out has a render() method which I think is what you're looking fo
I'm trying to get the method data.SetValue(...) working in the asynchronous callback in method getNames. Unfortunately it doesn't work. data.setValue(...) does work in the synchronous method createColumnChartView.
What could be the cause of this problem? Please explain why setting data doesn't work in getNames. Thanks in advance!
import java.util.ArrayList;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.visualization.client.DataTable;
import com.google.gwt.visualization.client.AbstractDataTable.ColumnType;
import com.google.gwt.visualization.client.visualizations.corechart.ColumnChart;
import com.google.gwt.visualization.client.visualizations.corechart.CoreChart;
import com.google.gwt.visualization.client.visualizations.corechart.Options;
import com.practicum.client.Product;
import com.practicum.client.rpc.ProductService;
import com.practicum.client.rpc.ProductServiceAsync;
public class DataOutColumnChart {
private final DataTable data = DataTable.create();
private final Options options = CoreChart.createOptions();
private final ProductServiceAsync productService = GWT.create(ProductService.class);
public DataOutColumnChart(Runnable runnable) {
}
public Widget createColumnChartView() {
/* create a datatable */
data.addColumn(ColumnType.STRING, "Price");
data.addColumn(ColumnType.NUMBER, "EUR");
data.addRows(2);
data.setValue(0, 0, "Bar 1");
data.setValue(0, 1, 123);
getNames();
/* create column chart */
options.setWidth(400);
options.setHeight(300);
options.setBackgroundColor("#e8e8e9");
return new ColumnChart(data, options);
}
public void getNames() {
productService.getNames(new AsyncCallback<ArrayList<Product>>() {
public void onFailure(Throwable caught) {
}
public void onSuccess(ArrayList<Product> result) {
for (Product p : result) {
data.setValue(0, 0, "Bar 2"); // DONT WORK, NOTHING HAPPENS
data.setValue(0, 1, 345); // DONT WORK, NOTHING HAPPENS
System.out.println("Bla bla test"); // THIS WORKS
}
}
});
}
}
The problem is occurring because you're setting data to a DataTable that has already been rendered. Your Asynchronous call in getNames() completes too slowly to affect the DataTable in time for the rendering of the ColumnChart. Even if it did complete fast enough, it would always be a race condition. Ideally, you would not actually render that chart until after you've received all necessary data from the RPC call.
Another option is to store a reference to that ColumnChart and call columnChart.draw(...) after you get your data back from RPC.
Edit:
Here's the example you requested.
import java.util.ArrayList;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.visualization.client.DataTable;
import com.google.gwt.visualization.client.AbstractDataTable.ColumnType;
import com.google.gwt.visualization.client.visualizations.corechart.ColumnChart;
import com.google.gwt.visualization.client.visualizations.corechart.CoreChart;
import com.google.gwt.visualization.client.visualizations.corechart.Options;
import com.practicum.client.Product;
import com.practicum.client.rpc.ProductService;
import com.practicum.client.rpc.ProductServiceAsync;
public class DataOutColumnChart {
private final DataTable data = DataTable.create();
private final Options options = CoreChart.createOptions();
private final ProductServiceAsync productService = GWT.create(ProductService.class);
private ColumnChart chart = null;
public DataOutColumnChart(Runnable runnable) {
}
public void initColumnChart() {
/* create a datatable */
data.addColumn(ColumnType.STRING, "Price");
data.addColumn(ColumnType.NUMBER, "EUR");
/* create column chart */
options.setWidth(400);
options.setHeight(300);
options.setBackgroundColor("#e8e8e9");
chart = new ColumnChart(data, options);
}
public void getNames() {
productService.getNames(new AsyncCallback<ArrayList<Product>>() {
public void onFailure(Throwable caught) {
}
public void onSuccess(ArrayList<Product> result) {
if (result != null && result.size() > 0) {
// if there is data...
data.addRows(result.size()); // add a row for each result
for (int i = 0; i < result.size(); i++) {
// loop through the results
Product product = result.get(i); // get out the product
// ...then set the column values for this row
data.setValue(i, 0, product.getSomeProperty());
data.setValue(i, 1, product.getSomeOtherProperty());
}
updateChart();
}
}
});
}
public void updateChart() {
chart.draw(data, options);
}
}