Jump to content
We've recently updated our Privacy Statement, available here ×
  • Updating Custom Data Source to JasperReports Server 6.0 and Higher


    elizam

    Issue

    Custom data sources created on jasperReports Server 5.6.1 or earlier fail on JasperReports Server 6.0. Opening the data source and clicking "Test Connection" gives an error such as the following:

    The bean with the name MyCustomDs does not have method createMyCustomDataSourceService

    Resolution

     

    ​There was a change in the Spring configuration for JR server 6.0 which changes how some of the existing Spring beans are made accessible for use by other beans. This can break existing custom data sources. This change specifically affects beans which implement the interface ReportDataSourceServiceFactory.

    Prior to 6.0, if code in JasperReports Server accessed a bean of this type, it would get the actual instance of the Spring bean as configured in the Spring XML file, and it could be cast to the concrete class. In 6.0 and later, these beans were intercepted in order to implement the profile attributes feature, and instead of seeing the actual instance, other code would see a dynamic proxy instead of the actual bean.

    Dynamic proxies are a Java feature which allows classes to be generated at runtime which implement any interface that can be loaded on the classpath. The resulting object can be cast to any of those interfaces, but doesn't correspond to any concrete class. There's plenty of info on the web about these, but

    here's one link:

    http://www.javaworld.com/article/2076233/java-se/explore-the-dynamic-proxy-api.html

    Since proxies can only represent interfaces, existing code that tries to cast the bean to a concrete class will break. Casting is usually done to get access to methods on a more specific class or interface. As long as the code is not casting the bean to a concrete class, it will work, so there are two ways to get around this problem:

    • If the code needs to access methods on an existing interface, just do a cast to that interface, or inject the property using the existing interface, so no cast is needed.
    • If the code needs to access methods that are not on an existing interface, simply create an interface with the methods needed, and have the target object implement that interface.

    For example, let's say you have a bean with id myBean that needs to access the jdbcDataSourceServiceFactory, configured like this:

    <bean id="jdbcDataSourceServiceFactory"
          class="com.jaspersoft.jasperserver.api.engine.jasperreports.service.impl.JdbcReportDataSourceServiceFactory">
        ... 
    </bean>
    

    myBean has a Spring config like this:

    <bean id="myBean" class="example.MyBean">
        <property name="jdbcDSSF" ref="jdbcDataSourceServiceFactory"/>
        ...
    </bean> 
    

    Code like this will break:

    public class MyBean {
        private ReportDataSourceServiceFactory jdbcDSSF;
        public ReportDataSourceServiceFactory getJdbcDSSF() { return jdbcDSSF; }
        // before 6.0, was called by Spring with the actual bean
        // 6.0 and after, is called with a dynamic proxy public void
        setJdbcDSSF(ReportDataSourceServiceFactory jdbcDSSF) { this.jdbcDSSF = jdbcDSSF; }
        public void doSomething() {
        // this code used to work, but now it will break
            ((JdbcReportDataSourceServiceFactory) jdbcDSSF).createService();
            ((JdbcReportDataSourceServiceFactory) jdbcDSSF).doSomethingElse(); 
        } 
    }
    

    Since the first call is a method which is part of the ReportDataSourceServiceFactory, the cast is unnecessary; to fix it, just leave it out:

    jdbcDSSF.createService();
    

    We are pretending that JdbcReportDataSourceServiceFactory has a method called doSomethingElse(); this method is not part of any interface, but you can create an interface that includes it:

    public interface MyDSSF extends ReportDataSourceServiceFactory { public void doSomethingElse(); }
    

    JdbcReportDataSourceServiceFactory would need modification so that it implements this new interface:

    public JdbcReportDataSourceServiceFactory implements MyDSSF { .... }
    

    You don't have to change the declaration in MyBean because Spring will generate a dynamic proxy implementing MyDSSF, but if you change the declaration, the code will be easier to understand because no casts will be necessary:

    public class MyBean { private MyDSSF jdbcDSSF; public MyDSSF getJdbcDSSF() { return jdbcDSSF; } // called with a dynamic proxy which implements all needed interfaces
    public void setJdbcDSSF(MyDSSF jdbcDSSF) { this.jdbcDSSF = jdbcDSSF; }
    public void doSomething() {
            // no need to cast jdbcDSSF.createService();
            jdbcDSSF.doSomethingElse(); }}
    

    User Feedback

    Recommended Comments

    There are no comments to display.



    Guest
    This is now closed for further comments

×
×
  • Create New...