Summary
I will show how you can override methods in XPages Extension Library components’ back-end classes. As an example, I will create a version of the JdbcConnectionManager that can handle dynamic (NSF-specific) username and password.
Background
I have for a while now tried to solve a problem with ExtLibx JDBC Connection Manager. This component utilizes connection property files located in WEB-INF/jdbc
folder. These files contain the username and password used when opening the connection. They are read-only and inherited from the NTF template. Thus, each NSF file sharing the same template connects to the SQL database with the same credentials. However, we want each NSF to use unique credentials. These should be stored in a “profile” document.
(Note that this effect can be achieved by basing the connections on dynamic JDBC URLs, however sadly connections created this way are not pooled.)
I have asked two questions about this subject on StackExchange:
Can JDBC connection files contain computed properties?
What is the best way of using dynamic JDBC username and password?
It was Stephan Wissel‘s suggestion about subclassing which finally led me to right direction. Rather than modifying (and therefore branching) the extlibx codebase, I created my own version of the <xe:jdbcConnectionManager>
component. Because of component inheritance, I only really need to override one or two functions.
Frantisek Kossuth showed me the starting point by directing me to the NSFFileJdbcProvider.java
. This class is responsible for loading the .jdbc file from the WEB-INF/jdbc folder. Sadly, the location is hard-coded.
Java classes
I found my target class (the old class which we will override) by tracing back from the NSFFileJdbcProvider.java
all the way to the class UIJdbcConnectionManager.java
. This class is used to create the back-end instance for every <xe:jdbcConnectionManager>
tag. (Note that this case is a bit special because: 1) usually there is only one of these tags per XPage and 2) nothing is rendered to the client.)
The original code works by creating DataSources and storing these in JNDI Registry (see JndiRegistry.java). However, since we have (currently) need for only one connection per NSF, I got an idea to bypass this step and create just one DataSource with my own parameters (including username and password). Then, when other components ask my new JdbcConnectionManager for a connection, my new class will return this connection.
The function in question is createConnection(). Here is the old version:
protected Connection createConnection() throws SQLException { FacesContext context = FacesContext.getCurrentInstance(); String url = getConnectionUrl(); if(StringUtil.isNotEmpty(url)) { Connection c = JdbcUtil.createConnectionFromUrl(context, url); initConnection(context,c); return c; } String name = getConnectionName(); if(StringUtil.isNotEmpty(name)) { Connection c = JdbcUtil.createNamedConnection(context, name); initConnection(context,c); return c; } throw new SQLException("No connection name nor url is provided in the ConnectionManager"); }
And here is the new class, which just overrides this one function:
package heeros.xsp.component; import java.sql.Connection; import java.sql.SQLException; import javax.faces.context.FacesContext; import com.ibm.designer.runtime.util.pool.PoolException; import com.ibm.xsp.extlib.component.jdbc.UIJdbcConnectionManager; import com.ibm.xsp.extlib.jdbc.datasource.xpages.JdbcPoolDataSource; public class HeerosConnectionManager extends UIJdbcConnectionManager { private JdbcPoolDataSource dataSource = null; @Override protected Connection createConnection() throws SQLException { heeros.utils.Utils.printConsole("[HeerosConnectionManager.java] entering createConnection() v3"); FacesContext context = FacesContext.getCurrentInstance(); try { if (this.dataSource == null) { String driver = "com.mysql.jdbc.Driver"; String url = "jdbc:mysql://1.2.3.4:3306"; String user = "username"; String password = "secret"; this.dataSource = new JdbcPoolDataSource(driver, url, user, password, 10, 20, 100, 0, 0, 0, 10000); } Connection c = dataSource.getConnection(); initConnection(context,c); return c; } catch (PoolException e) { e.printStackTrace(); return null; } } }
From this, it is easy to create a version that fetches the parameters from somewhere else. The number parameters passed to the JdbcPoolDataSource constructor define connection pool sizes and timeouts. Also, I’ll have to check whether this version honours the transaction isolation level specified. Note that this version allows only one DataSource while the original can handle multiple sources. It would be easy to extend this code to call the super()
as necessary based on some property added to the new component. This class was added to the NSF the usual way (we use a /src
folder under WEB-INF
).
Old .xsp-config file
After I had found the class I wanted to subclass, I searched for the .xsp-config file containing a reference to it. The file in question is extlib-jdbc.xsp-config
and it is located in \srcOpenNTF\eclipse\plugins\com.ibm.xsp.extlibx.relational\src\com\ibm\xsp\extlib\config
folder (relative to where the source was unzipped).
Here is the bit that interests us:
<component> <description>JDBC Connection Manager used to execute multiple data sources within the same JDBC transaction.</description> <display-name>JDBC Connection Manager</display-name> <component-type>com.ibm.xsp.extlib.jdbc.JdbcConnectionManager</component-type> <component-class>com.ibm.xsp.extlib.component.jdbc.UIJdbcConnectionManager</component-class> <group-type-ref>com.ibm.xsp.extlib.group.jdbc.connection_control</group-type-ref>
The component-class tag on row 5 tells us that this component uses UIJdbcConnectionManager to provide its back-end functionality.
Creating the new xsp-config file
Creating a new component was quite easy. As this component inherits all the
- Open Package Explorer.
- Locate the WebContent/WEB-INF folder.
- Right-click, choose New – Other – File. You can choose any name for the file, as long as the extension is .xsp-config.
Here is the file I created:
<faces-config> <faces-config-extension> <namespace-uri>http://www.heeros.com/xpages/component</namespace-uri> <default-prefix>hc</default-prefix> </faces-config-extension> <component> <display-name>Heeros JDBC Connection Manager</display-name> <component-type>com.ibm.xsp.extlib.jdbc.JdbcConnectionManager</component-type> <component-class>heeros.xsp.component.HeerosConnectionManager</component-class> <component-extension> <base-component-type>com.ibm.xsp.extlib.jdbc.JdbcConnectionManager</base-component-type> <tag-name>heerosConnectionManager</tag-name> </component-extension> </component> </faces-config>
Here is a short explanation of the contents:
The <faces-config-extension>
part defines a new namespace. This must be included in the beginning of the XPage. This way, two components can have the same name if they have different namespaces. I chose to use both a new name and a new namespace for clarity.
The <component-type>
is same as the parent.
The <component-class>
is the important bit: it tells the XPages engine to use our new (sub)class instead of the old one.
The <base-component-type>
is also important: it tells that this component inherits all properties from the original jdbcConnectionManager (saving us from lot of typing).
The <tag-name>
is what appears in the XPage source.
The tags are explained in more detail here.
The XPage
Here is a simple test page. Note that we have to add the “hc” namespace reference discussed earlier to the main <xp:view>
tag.
<?xml version="1.0" encoding="UTF-8"?> <xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xe="http://www.ibm.com/xsp/coreex" xmlns:xc="http://www.ibm.com/xsp/custom" xmlns:hc="http://www.heeros.com/xpages/component"> <xp:this.data> <xe:jdbcQuery var="jdbcQuery1" connectionManager="myconn" sqlQuery="SELECT * FROM mysql.db"> </xe:jdbcQuery> </xp:this.data> <hc:heerosConnectionManager id="myconn"> </hc:heerosConnectionManager> <xp:repeat id="repeat1" rows="30" value="#{jdbcQuery1}" var="row"> <xp:text escape="true" id="computedField1" value="#{row.Db}"></xp:text> </xp:repeat> </xp:view>
Conclusion
Here’s a procedure listing:
- Locate the .xsp-config file defining the parent component
- Find the component’s back-end class
- Locate the source code
- Create your own subclass and add it to the NSF
- Create a new .xsp-config file iside NSF and use
<base-component-type>
tag referencing the parent
This example was perhaps a bit obscure. The principle works however with any component you have the source code of. I hope that this helps the reader to make their own subclassed versions of the ExtLib components.