Jump to content
We've recently updated our Privacy Statement, available here ×
  • Building a custom datasource for Yahoo finance data


    gdmoreno

    Introduction

    In this article, we go through the process of creating a custom datasource. As an example to illustrate the process, we will use the financial data that you can pull down from the Yahoo Finance site. This is meant only as an example of how you can pull data dynamically to then make it available for use with JasperReports.

    Yahoo Finance is a leading financial news and data site, which stores historical financial data that we will use here. Yahoo Finance provides a URL-based API for requesting stock data, where you can dynamically build a URL with parameters to specify the stock, the date ranges, and the data you're interested in pulling down.

    For example, the URL below:

    http://ichart.finance.yahoo.com/table.csv?s=YHOO&d=0&e=28&f=2010&g=d&a=3&b=12&c=2009&ignore=.csv

    will return a CSV file for Yahoo (its ticker symbol is YHOO). The URL parameters stand for the following:

    sn Ticker symbol (YHOO in the example)
    a The "from month" - 1
    b The "from day" (two digits)
    c The "from year"
    d The "to month" - 1
    e The "to day" (two digits)
    f The "to year"
    g d for day, m for month, y for yearly

    The goal of this article is to show how you can pull this data down for use by JasperReports. To accomplish this, we will need to create an implementation of the JRDataSource interface.

    The JRDataSource Interface

    The JRDataSource interface consists of two method declarations: next() and getFieldValue(). The "next" method advances the pointer to the next record in the data JasperReports is parsing, while getFieldValue, which takes a field name as a parameter, returns the value for that field of the record that JasperReports is currently examining.

    A good way to remember what the data represented by the JRDataSource interface looks like is to think of it as a generic set of data rows, with each row having its own set of fields, whose values correspond to a known set of field names. In a way, it's an abstraction of what data organized in rows and columns looks like.

    public interface JRDataSource 
    { 
    /**
    * Tries to position the cursor on the next element in the data source.
    * @return true if there is a next record, false otherwise
    * @throws JRException if any error occurs while trying to move to the next element
    */
    public boolean next() throws JRException; 
    
    /**
    * Gets the field value for the current position.
    * @return an object containing the field value. The object type must be the field object type.
    */
    public Object getFieldValue(JRField jrField) throws JRException; 
    
    } 
    

    Implementing the YahooFinanceDataSource class

    Development Requirements

    To develop this example class, I created an Eclipse project and added the JAR files from JasperReports Server's WEB-INF/lib directory, as well as the JAR files from Tomcat's lib directory.

    The object variables

    To keep track of the individual rows in the datasource, I set up the rows as individual String values within an ArrayList object. The index variable serves to keep track of which row in the datasource the application is processing, while the listLength variable keeps track of the array's size.

     
    private ArrayList arrs; 
    private int index = -1; 
    private int listLength = 0;
    

    Implementing the constructor

    For this example, I set up a default constructor, although you can easily imagine other constructors with parameters that a user has entered in somewhere. Its job is to go out and call the URL we saw at the beginning of this article, and populate the arrs and listLength object variables.

    Once this object is populated with the right data, JasperReports can go ahead and use that datasource object to fill in a report.

     
    public YahooFinanceDataSource() { 
    
        String request = 'http://ichart.finance.yahoo.com/table.csv?s=YHOO&d=0&e=28&f=2010&g=d&a=3&b=12&c=2009&ignore=.csv'; 
        // this is the URL we saw at the beginning 
        InputStream rstream = null; 
        HttpClient client = new HttpClient(); 
        GetMethod method = new GetMethod(request); 
    
        arrs = new ArrayList(); 
    
        // Send GET request 
        int statusCode = 0; 
        try { 
            statusCode = client.executeMethod(method); 
        } catch (HttpException e) { 
            e.printStackTrace(); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    
        if (statusCode != HttpStatus.SC_OK) { 
            System.err.println('Method failed: ' + method.getStatusLine()); 
        } 
    
        // Get the response body 
        try { 
            rstream = method.getResponseBodyAsStream(); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    
        // Process the response from Yahoo! Web Services 
        int rowNum = 0; 
        BufferedReader br = new BufferedReader(new InputStreamReader(rstream)); 
        String line; 
        try { 
            while ((line = br.readLine()) != null) { 
                // Add the line into the array of strings 
                arrs.add(line); 
                rowNum++; 
            } 
            listLength = rowNum; 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } try { 
          br.close(); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    }
    

    Implementing the 'next' method

    Implementing the 'next' method is straightforward:

    public boolean next() throws JRException { 
        index++ ; return (index < listLength); 
    } 
    

    The index value is getting incremented, and then compared against the listLength variable, which stores the number of records that the datasource object contains. If the index value equals the listLength value, then the method returns false, indicating to the application that it's reached the datasource's last record.

    Implementing the 'getFieldValue' method

    Implementing the 'getFieldValue' involves returning the value of the requested field; the getFieldValue method gets a parameter specifying what field to return from the current record in the datasource. The implementation below is straightforward and quite typical for custom datasources; it's checking the requested fieldname for the known set of fields and returning the value that corresponds to that position in the datasource's record.

     
    public Object getFieldValue(JRField jrField) throws JRException { 
        Object value = null; 
    
        String fieldName = jrField.getName(); 
    
        String line = (String) arrs.get(index); 
        String [] data = line.split(','); 
    
        if (fieldName.equals('Date')) { 
            value = data[0]; 
        } else if (fieldName.equals('Open')) { 
            value = [1]; 
        } else if (fieldName.equals('High')) { 
            value = data[2]; 
        } else if (fieldName.equals('Low')) { 
            value = [3]; 
        } else if (fieldName.equals('Close')) { 
            value = [4]; 
        } else if (fieldName.equals('Volume')) { 
            value = [5]; 
        } else if (fieldName.equals('Adj Close')) { 
            value = [6]; 
        } return value ; 
    } 
    

    Other methods

    You can also add other methods and calls to helper classes; once you're in the custom development world, you can make calls to whatever's at your disposal. In my own case, I added the method below, to make the field names I'm defining easily available to a client of the YahooFinanceDataSource class.

     
    public static String fieldNames() { 
        String fieldNames = {'Date', 'Open', 'High', 'Low', 'Close', 'Volume', 'Adj Close'}; 
        return fieldNames; 
    } 
    

    Using the custom datasource class

    To use the custom datasource in a server environment, you'll need to create associated ExecuterFactory and QueryExecuter classes (these would both be custom implementations of interfaces in JasperReports Server), which is outside the scope of this article.

    An easy way to test this custom datasource is to create a custom DataSourceProvider class that uses the custom datasource class, and deploy these two classes for use within iReport. We look at that idea in an article you can find here: Using a custom DataSourceProvider in iReport with custom datasources

     


    User Feedback

    Recommended Comments

    There are no comments to display.



    Guest
    This is now closed for further comments

×
×
  • Create New...