theodoretsai opened a new issue, #26805:
URL: https://github.com/apache/shardingsphere/issues/26805

   ## Bug Report
   
   NullPointerException caused by Non Compatible DataSource type.
   
   I don't know if this reallty qualifies as a bug report or is a designed 
limitation of ShardingSphere, when using custom implementations of DataSource 
the framework might not correctly obtain metadata and cause 
NullPointerException.
   
   ### Which version of ShardingSphere did you use?
   
   5.3.1
   
   ### Which project did you use? ShardingSphere-JDBC or ShardingSphere-Proxy?
   
   ShardingSphere-JDBC
   
   ### Expected behavior
   
   Create DataSource successfully
   
   ### Actual behavior
   
   NullPointerException when trying to obtain ResourceMetaData
   
   ### Reason analyze (If you can)
   StackTrace
   ```
   java.lang.NullPointerException: null
        at 
org.apache.shardingsphere.infra.metadata.database.resource.ShardingSphereResourceMetaData.createDataSourceMetaDataMap(ShardingSphereResourceMetaData.java:71)
        at 
org.apache.shardingsphere.infra.metadata.database.resource.ShardingSphereResourceMetaData.<init>(ShardingSphereResourceMetaData.java:55)
   ...
   ```
   Database properties are obtained using reflection in 
`DataSourcePropertiesCreator.java`
   
   ```
       private static Map<String, Object> createProperties(final DataSource 
dataSource) {
           Map<String, Object> result = new LinkedHashMap<>();
           Optional<DataSourcePoolMetaData> poolMetaData = 
TypedSPIRegistry.findRegisteredService(DataSourcePoolMetaData.class, 
dataSource.getClass().getName());
           for (Entry<String, Object> entry : new 
DataSourceReflection(dataSource).convertToProperties().entrySet()) {
               String propertyName = entry.getKey();
               Object propertyValue = entry.getValue();
               if (!poolMetaData.isPresent() || isValidProperty(propertyName, 
propertyValue, poolMetaData.get()) && 
!poolMetaData.get().getTransientFieldNames().contains(propertyName)) {
                   result.put(propertyName, propertyValue);
               }
           }
           return result;
       }
   ```
   
   Getters are scanned and converted to properties.
   From what I understand: for different implementations of DataSource a 
`DataSourcePoolMetaData` can be implemented for the DataSource Type and 
synonyms of fields can be provided in case there are no getters with standard 
names.
   
   
   The Reflection implementation of finding properties doesn't account for 
DataSource implementation that can't directly obtain username and url via 
getters, resulting in NullpointerException at 
ShardingSphereResourceMetaData.java:71:
   
   ```
       private Map<String, DataSourceMetaData> 
createDataSourceMetaDataMap(final Map<String, DataSource> dataSources, final 
Map<String, DatabaseType> storageTypes) {
           Map<String, DataSourceMetaData> result = new 
LinkedHashMap<>(dataSources.size(), 1);
           for (Entry<String, DataSource> entry : dataSources.entrySet()) {
               Map<String, Object> standardProps = 
DataSourcePropertiesCreator.create(entry.getValue()).getConnectionPropertySynonyms().getStandardProperties();
               DatabaseType storageType = storageTypes.get(entry.getKey());
   >>>>            result.put(entry.getKey(), 
storageType.getDataSourceMetaData(standardProps.get("url").toString(), 
standardProps.get("username").toString()));      
           }
           return result;
       }
   ```
   
   To avoid this problem the solution I've found now is to wrap my DataSource 
with class that gives the DatabaseReflection some getters to Scan:
   ```
   public class MyWorkaroudDataSource extends DynamicDataSource {
   
       // .... implementations
   
       @SuppressWarnings({"unused"})
       //used by ShardingSphere reflection to build metadata
       public String getUsername() {
           // implementation
       }
   
       @SuppressWarnings("unused")
       //used by ShardingSphere reflection to build metadata
           // implementation
       }
   }
   ```
   But is quite ugly.
   
   Is there actually a correct way to do this that I've missed? 
   Also what is the reason the url and username must be obtained in this way? 
Couldn't they be obtained from .getConnection().getMetaData() without resorting 
to reflection?
   
   ### Steps to reproduce the behavior, such as: SQL to execute, sharding rule 
configuration, when exception occur etc.
   
   Wrap a DataSource Object with another DataSourceClass that doesn't have any 
getters for the DatabaseReflection to scan such as:
   
   ```
   public class HiddenGettersDataSource implements DataSource {
   
       private final DataSource dataSource;
   
       public HiddenGettersDataSource (DataSource source) {
           this.dataSource = source;
       }
   
       @Override
       public Connection getConnection() throws SQLException {
           Connection connection = dataSource.getConnection();
           return connection;
       }
   
       @Override
       public Connection getConnection(String username, String password) throws 
SQLException {
           Connection connection = dataSource.getConnection(username, password);
           return connection;
       }
   
       @Override
       public PrintWriter getLogWriter() throws SQLException {
           return dataSource.getLogWriter();
       }
   
       @Override
       public void setLogWriter(PrintWriter out) throws SQLException {
           dataSource.setLogWriter(out);
       }
   
       @Override
       public void setLoginTimeout(int seconds) throws SQLException {
           dataSource.setLoginTimeout(seconds);
       }
   
       @Override
       public int getLoginTimeout() throws SQLException {
           return dataSource.getLoginTimeout();
       }
   
       @Override
       public <T> T unwrap(Class<T> iface) throws SQLException {
           return dataSource.unwrap(iface);
       }
   
       @Override
       public boolean isWrapperFor(Class<?> iface) throws SQLException {
           return dataSource.isWrapperFor(iface);
       }
   
       @Override
       public Logger getParentLogger() throws SQLFeatureNotSupportedException {
           return dataSource.getParentLogger();
       }
   }
   ```
   
   
   ### Example codes for reproduce this issue (such as a github link).
   
   Sorry I can't provide code but should be clear enough from the description.
   
   
   Thank you.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: 
[email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to