Skip to content

Commit 8b6f4bd

Browse files
committed
Package dependencies and improve test coverage
1 parent eb6ec7d commit 8b6f4bd

11 files changed

Lines changed: 603 additions & 11 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ src/main/resources/testcredentials.properties
1313
.DS_Store
1414
local/
1515
src/test/resources/testcredentials.properties
16+
/.vscode

README.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,7 @@ The latest (bleeding edge) package can be downloaded in the [packages section](h
2424
![Where to download latest cyHANA jar](doc/img/download_package.png)
2525

2626
### Setup
27-
After getting the latest JAR package, you can install the plugin using the [Cytoscape App Manager](http://manual.cytoscape.org/en/stable/App_Manager.html).
28-
29-
The plugin makes use of the SAP HANA JDBC client, which additionally needs to be included on the classpath. The latest version can be retrieved from the [SAP Development Tools](https://tools.hana.ondemand.com/#hanatools) (look for [ngdcb-latest.jar](https://tools.hana.ondemand.com/additional/ngdbc-latest.jar)).
30-
31-
On a Mac, you can copy `ngdbc-latest.jar` to `/Applications/Cytoscape_vX.X.X/framework/lib/openjfx/mac`. A similar folder exists on Windows machines within the Cytoscape installation.
32-
33-
After installation, you should be able to find the plugin under `Apps` > `SAP HANA`.
27+
After getting the latest JAR package, you can install the plugin using the [Cytoscape App Manager](http://manual.cytoscape.org/en/stable/App_Manager.html). After installation, you should be able to find the plugin under `Apps` > `SAP HANA`.
3428

3529
![The Apps menu with installed cyHANA plugin](doc/img/apps_menu.png)
3630

pom.xml

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<bundle.symbolicName>org.sap.cytoscape</bundle.symbolicName>
1616
<bundle.namespace>org.sap.cytoscape.internal</bundle.namespace>
1717

18-
<maven-bundle-plugin.version>5.1.8</maven-bundle-plugin.version>
18+
<maven-bundle-plugin.version>5.1.9</maven-bundle-plugin.version>
1919
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
2020

2121
<junit.version>4.13.2</junit.version>
@@ -27,7 +27,7 @@
2727
<maven.compiler.source>17</maven.compiler.source>
2828
<maven.compiler.target>17</maven.compiler.target>
2929

30-
<ngdbc.version>2.9.16</ngdbc.version>
30+
<ngdbc.version>2.17.7</ngdbc.version>
3131
</properties>
3232

3333
<repositories>
@@ -88,14 +88,38 @@
8888
<extensions>true</extensions>
8989
<configuration>
9090
<instructions>
91+
<multi-release>true</multi-release>
9192
<Bundle-SymbolicName>${bundle.symbolicName}</Bundle-SymbolicName>
9293
<Bundle-Version>${project.version}</Bundle-Version>
9394
<Export-Package>!${bundle.namespace}.*</Export-Package>
9495
<Private-Package>${bundle.namespace}.*</Private-Package>
9596
<Bundle-Activator>${bundle.namespace}.CyActivator</Bundle-Activator>
97+
<Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
98+
<Embed-Transitive>true</Embed-Transitive>
99+
<Embed-StripVersion>true</Embed-StripVersion>
100+
<Embed-Directory>lib</Embed-Directory>
101+
<Import-Package>*;resolution:=optional</Import-Package>
96102
</instructions>
97103
</configuration>
98104
</plugin>
105+
<plugin>
106+
<groupId>org.apache.maven.plugins</groupId>
107+
<artifactId>maven-failsafe-plugin</artifactId>
108+
<version>3.2.5</version>
109+
<executions>
110+
<execution>
111+
<goals>
112+
<goal>integration-test</goal>
113+
<goal>verify</goal>
114+
</goals>
115+
</execution>
116+
</executions>
117+
<configuration>
118+
<systemPropertyVariables>
119+
<bundle.jar>${project.build.directory}/${project.build.finalName}.jar</bundle.jar>
120+
</systemPropertyVariables>
121+
</configuration>
122+
</plugin>
99123
</plugins>
100124
</build>
101125

@@ -110,21 +134,25 @@
110134
<groupId>org.ops4j.pax.logging</groupId>
111135
<artifactId>pax-logging-api</artifactId>
112136
<version>${pax.logging.version}</version>
137+
<scope>provided</scope>
113138
</dependency>
114139
<dependency>
115140
<groupId>org.ops4j.pax.logging</groupId>
116141
<artifactId>pax-logging-service</artifactId>
117142
<version>${pax.logging.version}</version>
143+
<scope>provided</scope>
118144
</dependency>
119145
<dependency>
120146
<groupId>org.osgi</groupId>
121147
<artifactId>osgi.core</artifactId>
122148
<version>${osgi.api.version}</version>
149+
<scope>provided</scope>
123150
</dependency>
124151
<dependency>
125152
<groupId>junit</groupId>
126153
<artifactId>junit</artifactId>
127154
<version>${junit.version}</version>
155+
<scope>test</scope>
128156
</dependency>
129157
<dependency>
130158
<groupId>com.sap.cloud.db.jdbc</groupId>

src/main/java/org/sap/cytoscape/internal/hdb/HanaConnectionManager.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,17 @@ public void connect(String host, String port, Properties connectionProperties) t
5656
this.connection = null;
5757

5858
try {
59+
Class.forName("com.sap.db.jdbc.Driver");
5960
this.connection = DriverManager.getConnection("jdbc:sap://" + host + ":" + port + "/", connectionProperties);
6061

6162
if (this.connection.isValid(1500)){
6263
this.buildVersion = this.executeQuerySingleValue(this.sqlStrings.getProperty("GET_BUILD"), null, String.class);
6364
}
6465

6566
info("Connected to HANA database: "+host+" ("+this.buildVersion+")");
67+
} catch (ClassNotFoundException e) {
68+
err("HANA JDBC driver not found: " + e.getMessage());
69+
throw new SQLException("HANA JDBC driver (ngdbc) not found on classpath", e);
6670
} catch (SQLException e) {
6771
err("Error connecting to HANA instance:"+host);
6872
err(e.toString());

src/test/java/HanaConnectionCredentialsTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,38 @@ public void testGenerateAdvancedProperties(){
8080

8181
}
8282

83+
@Test
84+
public void testGenerateAdvancedProperties_emptyValue(){
85+
// "key=".split("=") returns ["key"] only — Java drops the trailing empty token.
86+
// Accessing index [1] then throws ArrayIndexOutOfBoundsException, which is caught
87+
// and rethrown as HanaConnectionManagerException.
88+
// This test documents the actual behaviour of the existing code.
89+
HanaConnectionCredentials cred = new HanaConnectionCredentials(null, null, null, null, null, null);
90+
try {
91+
cred.advancedProperties = "key=";
92+
cred.generateAdvancedProperties();
93+
fail("Expected HanaConnectionManagerException for entry with empty value");
94+
} catch (HanaConnectionManagerException e) {
95+
// expected — empty value after '=' is not supported by current implementation
96+
} catch (Exception e) {
97+
fail("Unexpected exception type: " + e);
98+
}
99+
}
100+
101+
@Test
102+
public void testGenerateAdvancedProperties_valueContainsEquals(){
103+
// Current implementation splits on ALL '=' characters, so 'key=val=ue'
104+
// results in key="key", value="val" (the trailing "=ue" is silently dropped).
105+
// This test documents the actual behaviour of the existing code.
106+
HanaConnectionCredentials cred = new HanaConnectionCredentials(null, null, null, null, null, null);
107+
try {
108+
cred.advancedProperties = "key=val=ue";
109+
Properties props = cred.generateAdvancedProperties();
110+
Assert.assertEquals(1, props.size());
111+
Assert.assertEquals("val", props.getProperty("key"));
112+
} catch (Exception e) {
113+
fail();
114+
}
115+
}
116+
83117
}

src/test/java/HanaConnectionManagerTest.java

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.sap.cytoscape.internal.utils.IOUtils;
88

99
import java.io.IOException;
10+
import java.lang.reflect.Field;
1011
import java.sql.SQLException;
1112
import java.sql.Types;
1213
import java.util.*;
@@ -346,4 +347,135 @@ public void testOnlyEdgeWorkspace(){
346347
Assert.assertEquals(4, sspWorkspace.getNodeTable().size());
347348
}
348349

350+
@Test
351+
public void testIsConnected_falseBeforeConnect() throws IOException {
352+
HanaConnectionManager freshManager = new HanaConnectionManager();
353+
Assert.assertFalse("A freshly created manager must not report as connected", freshManager.isConnected());
354+
}
355+
356+
@Test
357+
public void testGetInstanceIdentifier_nonEmptyAfterConnect() {
358+
try {
359+
String identifier = connectionManager.getInstanceIdentifier();
360+
Assert.assertNotNull("getInstanceIdentifier must not return null", identifier);
361+
Assert.assertTrue("getInstanceIdentifier must return a non-empty string", identifier.length() > 0);
362+
} catch (SQLException e) {
363+
Assert.fail(e.toString());
364+
}
365+
}
366+
367+
@Test
368+
public void testLoadGraphWorkspaceByDbObject() {
369+
HanaGraphWorkspace ws = null;
370+
try {
371+
ws = connectionManager.loadGraphWorkspace(new HanaDbObject(testSchema, "SSP"));
372+
} catch (Exception e) {
373+
Assert.fail(e.toString());
374+
}
375+
Assert.assertNotNull(ws);
376+
Assert.assertTrue(ws.isMetadataComplete());
377+
Assert.assertEquals(6, ws.getEdgeTable().size());
378+
Assert.assertEquals(4, ws.getNodeTable().size());
379+
}
380+
381+
@Test
382+
public void testCreateGraphWorkspace() throws Exception {
383+
String nodeTableName = "TEST_CW_NODES";
384+
String edgeTableName = "TEST_CW_EDGES";
385+
String workspaceName = "TEST_CW_WS";
386+
387+
HanaDbObject nodeTable = new HanaDbObject(testSchema, nodeTableName);
388+
HanaDbObject edgeTable = new HanaDbObject(testSchema, edgeTableName);
389+
390+
// Create the backing tables
391+
connectionManager.createTable(nodeTable, Arrays.asList(
392+
new HanaColumnInfo(testSchema, nodeTableName, "NODE_ID", Types.INTEGER, true)
393+
));
394+
connectionManager.createTable(edgeTable, Arrays.asList(
395+
new HanaColumnInfo(testSchema, edgeTableName, "EDGE_ID", Types.INTEGER, true),
396+
new HanaColumnInfo(testSchema, edgeTableName, "SRC", Types.INTEGER, false, true),
397+
new HanaColumnInfo(testSchema, edgeTableName, "TGT", Types.INTEGER, false, true)
398+
));
399+
400+
// Build a HanaGraphWorkspace with all required metadata.
401+
// nodeTableDbObject and edgeTableDbObject have no setters, so we set them via reflection.
402+
HanaGraphWorkspace ws = new HanaGraphWorkspace(new HanaDbObject(testSchema, workspaceName));
403+
setField(ws, "nodeTableDbObject", nodeTable);
404+
setField(ws, "edgeTableDbObject", edgeTable);
405+
ws.addNodeKeyCol(new HanaColumnInfo(testSchema, nodeTableName, "NODE_ID", Types.INTEGER, true));
406+
ws.addEdgeKeyCol(new HanaColumnInfo(testSchema, edgeTableName, "EDGE_ID", Types.INTEGER, true));
407+
ws.addEdgeSourceCol(new HanaColumnInfo(testSchema, edgeTableName, "SRC", Types.INTEGER, false));
408+
ws.addEdgeTargetCol(new HanaColumnInfo(testSchema, edgeTableName, "TGT", Types.INTEGER, false));
409+
410+
connectionManager.createGraphWorkspace(ws);
411+
412+
// Verify it appears in the listing
413+
boolean found = false;
414+
for (HanaDbObject obj : connectionManager.listGraphWorkspaces()) {
415+
if (obj.schema.equals(testSchema) && obj.name.equals(workspaceName)) {
416+
found = true;
417+
break;
418+
}
419+
}
420+
Assert.assertTrue("Created workspace must appear in listGraphWorkspaces()", found);
421+
}
422+
423+
/** Reflective field setter for package-private / private fields in HanaGraphWorkspace. */
424+
private static void setField(Object target, String fieldName, Object value) throws Exception {
425+
Field f = target.getClass().getDeclaredField(fieldName);
426+
f.setAccessible(true);
427+
f.set(target, value);
428+
}
429+
430+
@Test(expected = SQLException.class)
431+
public void testExecuteInvalidSqlThrows() throws SQLException {
432+
connectionManager.execute("THIS IS NOT VALID SQL");
433+
}
434+
435+
@Test
436+
public void testBulkInsertWithNullValue() {
437+
HanaDbObject targetTable = new HanaDbObject(testSchema, "TEST_BULK_NULL_TABLE");
438+
List<HanaColumnInfo> cols = Arrays.asList(
439+
new HanaColumnInfo(targetTable.schema, targetTable.name, "COL1", Types.NVARCHAR),
440+
new HanaColumnInfo(targetTable.schema, targetTable.name, "COL2", Types.NVARCHAR)
441+
);
442+
443+
Map<String, Object> record = new HashMap<>();
444+
record.put("COL1", "present");
445+
record.put("COL2", null); // null value — exercises the setNull branch in executeBatch
446+
447+
try {
448+
connectionManager.createTable(targetTable, cols);
449+
connectionManager.bulkInsertData(targetTable, cols, Collections.singletonList(record));
450+
int count = connectionManager.executeQuerySingleValue(String.format(
451+
sqlStringsTest.getProperty("COUNT_RECORDS"),
452+
targetTable.schema, targetTable.name
453+
), null, Integer.class);
454+
Assert.assertEquals(1, count);
455+
} catch (SQLException e) {
456+
Assert.fail(e.toString());
457+
}
458+
}
459+
460+
@Test
461+
public void testCreateTableWithPrimaryKey() {
462+
String tableName = "TEST_PK_TABLE";
463+
HanaDbObject pkTable = new HanaDbObject(testSchema, tableName);
464+
List<HanaColumnInfo> cols = Arrays.asList(
465+
new HanaColumnInfo(testSchema, tableName, "ID", Types.INTEGER, true),
466+
new HanaColumnInfo(testSchema, tableName, "NAME", Types.NVARCHAR)
467+
);
468+
469+
try {
470+
connectionManager.createTable(pkTable, cols);
471+
HanaQueryResult result = connectionManager.executeQueryList(String.format(
472+
sqlStringsTest.getProperty("GENERIC_SELECT_PROJECTION"),
473+
"*", testSchema, tableName
474+
));
475+
Assert.assertEquals(2, result.getColumnMetadata().length);
476+
} catch (SQLException e) {
477+
Assert.fail(e.toString());
478+
}
479+
}
480+
349481
}

0 commit comments

Comments
 (0)