1717import com .cs .persistence .util .PageUtils ;
1818import com .zaxxer .hikari .HikariDataSource ;
1919import lombok .SneakyThrows ;
20+ import lombok .extern .slf4j .Slf4j ;
2021import org .apache .commons .lang3 .StringUtils ;
2122import org .apache .commons .lang3 .tuple .Pair ;
2223import org .springframework .stereotype .Service ;
3031import java .util .regex .Matcher ;
3132import java .util .stream .Collectors ;
3233
34+ @ Slf4j
3335@ Service
3436public class DataSourceService {
3537
38+ /**
39+ * Cap for the driver message carried out by a connection test; driver stacks can be far longer than
40+ * anything a user needs to read in a toast.
41+ */
42+ private static final int CONNECT_ERROR_MESSAGE_MAX_LENGTH = 500 ;
43+
3644 @ Resource
3745 private DataSourceDao dataSourceDao ;
3846
@@ -114,11 +122,17 @@ public void preTest(DataSourceBaseRequest request) {
114122 .getVersionDriverFile (dataSourceEntity .getType (),
115123 dataSourceEntity .getVersion ());
116124 String driverPath = driverPathFile .getAbsolutePath ();
117- HikariDataSource ds = DataSourceUtils .createDataSource (dataSourceEntity , driverPath );
118125 try {
119- testConnection (ds , request .getType ());
120- } finally {
121- DataSourceUtils .closeHikariDataSource (ds );
126+ HikariDataSource ds = DataSourceUtils .createDataSource (dataSourceEntity , driverPath );
127+ try {
128+ testConnection (ds , request .getType ());
129+ } finally {
130+ DataSourceUtils .closeHikariDataSource (ds );
131+ }
132+ } catch (CommonException e ) {
133+ throw e ;
134+ } catch (RuntimeException e ) {
135+ throw connectFailure (e );
122136 }
123137 }
124138
@@ -131,8 +145,43 @@ public void testDataSource(Long id) {
131145 .getVersionDriverFile (dataSourceEntity .getType (),
132146 dataSourceEntity .getVersion ());
133147 String driverPath = driverPathFile .getAbsolutePath ();
134- HikariDataSource ds = DataSourceUtils .getHikariDataSource (dataSourceEntity , driverPath );
135- testConnection (ds , dataSourceEntity .getType ());
148+ try {
149+ HikariDataSource ds = DataSourceUtils .getHikariDataSource (dataSourceEntity , driverPath );
150+ testConnection (ds , dataSourceEntity .getType ());
151+ } catch (CommonException e ) {
152+ throw e ;
153+ } catch (RuntimeException e ) {
154+ throw connectFailure (e );
155+ }
156+ }
157+
158+ /**
159+ * A connection test exists to tell the caller why a datasource does not work, so the driver/server
160+ * message is raised as a business error (code 10, business failures stay HTTP 200) instead of being
161+ * masked as a generic internal error. The stack still goes to the log for support.
162+ */
163+ static CommonException connectFailure (Throwable cause ) {
164+ String message = causeMessage (cause );
165+ log .warn ("Datasource connection test failed: {}" , message , cause );
166+ CommonException exception = new CommonException (ResponseErrorCode .ERROR_CANNOT_CONNECT_REMOTE ,
167+ "datasource.connect.failed" , message );
168+ exception .initCause (cause );
169+ return exception ;
170+ }
171+
172+ /**
173+ * Deepest cause message: JDBC driver stacks (ODPS and friends) put the actionable text - permission
174+ * denied, unknown host, bad credentials - at the bottom, while the outer layers only repeat it.
175+ * Whitespace is collapsed so a multi-line driver dump stays readable in one response field.
176+ */
177+ static String causeMessage (Throwable cause ) {
178+ Throwable root = cause ;
179+ while (null != root .getCause () && root .getCause () != root ) {
180+ root = root .getCause ();
181+ }
182+ String message = StringUtils .defaultIfBlank (root .getMessage (), cause .getMessage ());
183+ message = StringUtils .defaultIfBlank (message , root .getClass ().getName ());
184+ return StringUtils .abbreviate (message .replaceAll ("\\ s+" , " " ).trim (), CONNECT_ERROR_MESSAGE_MAX_LENGTH );
136185 }
137186
138187 public void createDataSource (DataSourceSaveRequest request ) {
0 commit comments