diff --git a/README.md b/README.md index c0cf521c2..5abd2f671 100644 --- a/README.md +++ b/README.md @@ -61,23 +61,12 @@ driver.close(); ## Usage examples -Driver creation: +Driver lifecycle: ```javascript // Create a driver instance, for the user neo4j with password neo4j. // It should be enough to have a single driver per database per application. var driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j")); -// Register a callback to know if driver creation was successful: -driver.onCompleted = function () { - // proceed with using the driver, it was successfully instantiated -}; - -// Register a callback to know if driver creation failed. -// This could happen due to wrong credentials or database unavailability: -driver.onError = function (error) { - console.log('Driver instantiation failed', error); -}; - // Close the driver when application exits. // This closes all used network connections. driver.close(); diff --git a/src/v1/driver.js b/src/v1/driver.js index 628624158..71f76052f 100644 --- a/src/v1/driver.js +++ b/src/v1/driver.js @@ -24,6 +24,7 @@ import StreamObserver from './internal/stream-observer'; import {newError, SERVICE_UNAVAILABLE} from './error'; import {DirectConnectionProvider} from './internal/connection-providers'; import Bookmark from './internal/bookmark'; +import ConnectivityVerifier from './internal/connectivity-verifier'; const READ = 'READ', WRITE = 'WRITE'; /** @@ -72,6 +73,21 @@ class Driver { * @protected */ this._connectionProvider = null; + + this._onCompleted = null; + } + + get onCompleted() { + return this._onCompleted; + } + + set onCompleted(callback) { + this._onCompleted = callback; + if (this._onCompleted) { + const connectionProvider = this._getOrCreateConnectionProvider(); + const connectivityVerifier = new ConnectivityVerifier(connectionProvider, this._onCompleted); + connectivityVerifier.verify(); + } } /** @@ -219,16 +235,6 @@ class _ConnectionStreamObserver extends StreamObserver { this._hasFailed = true; } } - - onCompleted(message) { - if (this._driver.onCompleted) { - this._driver.onCompleted(message); - } - - if (this._observer && this._observer.onComplete) { - this._observer.onCompleted(message); - } - } } /** diff --git a/src/v1/internal/connectivity-verifier.js b/src/v1/internal/connectivity-verifier.js new file mode 100644 index 000000000..cbaedb8e4 --- /dev/null +++ b/src/v1/internal/connectivity-verifier.js @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2002-2017 "Neo Technology,"," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ConnectionHolder from './connection-holder'; +import {READ} from '../driver'; +import StreamObserver from './stream-observer'; + +/** + * Verifies connectivity using the given connection provider. + */ +export default class ConnectivityVerifier { + + /** + * @constructor + * @param {ConnectionProvider} connectionProvider the provider to obtain connections from. + * @param {function} successCallback a callback to invoke when verification succeeds. + */ + constructor(connectionProvider, successCallback) { + this._connectionProvider = connectionProvider; + this._successCallback = successCallback; + } + + verify() { + acquireAndReleaseDummyConnection(this._connectionProvider).then(serverInfo => { + if (this._successCallback) { + this._successCallback(serverInfo); + } + }).catch(ignoredError => { + }); + } +} + +/** + * @private + * @param {ConnectionProvider} connectionProvider the provider to obtain connections from. + * @return {Promise} promise resolved with server info or rejected with error. + */ +function acquireAndReleaseDummyConnection(connectionProvider) { + const connectionHolder = new ConnectionHolder(READ, connectionProvider); + connectionHolder.initializeConnection(); + const dummyObserver = new StreamObserver(); + const connectionPromise = connectionHolder.getConnection(dummyObserver); + + return connectionPromise.then(connection => { + // able to establish a connection + return connectionHolder.close().then(() => connection.server); + }).catch(error => { + // failed to establish a connection + return connectionHolder.close().catch(ignoredError => { + // ignore connection release error + }).then(() => { + return Promise.reject(error); + }); + }); +} diff --git a/src/v1/internal/server-version.js b/src/v1/internal/server-version.js index 1c25d71fc..0675bb577 100644 --- a/src/v1/internal/server-version.js +++ b/src/v1/internal/server-version.js @@ -91,10 +91,12 @@ function compareInts(x, y) { return (x < y) ? -1 : ((x === y) ? 0 : 1); } +const VERSION_3_1_0 = new ServerVersion(3, 1, 0); const VERSION_3_2_0 = new ServerVersion(3, 2, 0); export { ServerVersion, + VERSION_3_1_0, VERSION_3_2_0 }; diff --git a/test/internal/connectivity-verifier.test.js b/test/internal/connectivity-verifier.test.js new file mode 100644 index 000000000..498ad4c06 --- /dev/null +++ b/test/internal/connectivity-verifier.test.js @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2002-2017 "Neo Technology,"," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ConnectivityVerifier from '../../src/v1/internal/connectivity-verifier'; +import {SingleConnectionProvider} from '../../src/v1/internal/connection-providers'; +import FakeConnection from './fake-connection'; + +describe('ConnectivityVerifier', () => { + + it('should call success callback when able to acquire and release a connection', done => { + const connectionPromise = Promise.resolve(new FakeConnection()); + const connectionProvider = new SingleConnectionProvider(connectionPromise); + + const verifier = new ConnectivityVerifier(connectionProvider, () => { + done(); + }); + + verifier.verify(); + }); + +}); diff --git a/test/types/v1/driver.test.ts b/test/types/v1/driver.test.ts index b79df9ec5..53f1f718b 100644 --- a/test/types/v1/driver.test.ts +++ b/test/types/v1/driver.test.ts @@ -30,6 +30,7 @@ import Driver, { import {Parameters} from "../../../types/v1/statement-runner"; import Session from "../../../types/v1/session"; import {Neo4jError} from "../../../types/v1/error"; +import {ServerInfo} from "../../../types/v1/result-summary"; const dummy: any = null; @@ -87,11 +88,12 @@ session1.run("RETURN 1").then(result => { const close: void = driver.close(); -driver.onCompleted = (metadata: { server: string }) => { - console.log(metadata.server); +driver.onCompleted = (serverInfo: ServerInfo) => { + console.log(serverInfo.version); + console.log(serverInfo.address); }; -driver.onCompleted({server: "Neo4j/3.2.0"}); +driver.onCompleted({version: "Neo4j/3.2.0", address: "localhost:7687"}); driver.onError = (error: Neo4jError) => { console.log(error); diff --git a/test/v1/driver.test.js b/test/v1/driver.test.js index fbdf65d6b..748bccc7b 100644 --- a/test/v1/driver.test.js +++ b/test/v1/driver.test.js @@ -112,12 +112,10 @@ describe('driver', () => { driver = neo4j.driver("bolt://localhost", sharedNeo4j.authToken); // Expect - driver.onCompleted = meta => { + driver.onCompleted = server => { + expect(server.address).toBeDefined(); done(); }; - - // When - startNewTransaction(driver); }); it('should be possible to pass a realm with basic auth tokens', done => { @@ -125,12 +123,10 @@ describe('driver', () => { driver = neo4j.driver("bolt://localhost", neo4j.auth.basic(sharedNeo4j.username, sharedNeo4j.password, "native")); // Expect - driver.onCompleted = meta => { + driver.onCompleted = server => { + expect(server.address).toBeDefined(); done(); }; - - // When - startNewTransaction(driver); }); it('should be possible to create custom auth tokens', done => { @@ -138,12 +134,10 @@ describe('driver', () => { driver = neo4j.driver("bolt://localhost", neo4j.auth.custom(sharedNeo4j.username, sharedNeo4j.password, "native", "basic")); // Expect - driver.onCompleted = meta => { + driver.onCompleted = server => { + expect(server.address).toBeDefined(); done(); }; - - // When - startNewTransaction(driver); }); it('should be possible to create custom auth tokens with additional parameters', done => { @@ -151,12 +145,10 @@ describe('driver', () => { driver = neo4j.driver("bolt://localhost", neo4j.auth.custom(sharedNeo4j.username, sharedNeo4j.password, "native", "basic", {secret: 42})); // Expect - driver.onCompleted = () => { + driver.onCompleted = server => { + expect(server.address).toBeDefined(); done(); }; - - // When - startNewTransaction(driver); }); it('should fail nicely when connecting with routing to standalone server', done => { diff --git a/test/v1/examples.test.js b/test/v1/examples.test.js index 91554e08d..a9a4afd55 100644 --- a/test/v1/examples.test.js +++ b/test/v1/examples.test.js @@ -102,14 +102,9 @@ describe('examples', () => { // end::basic-auth[] driver.onCompleted = () => { + driver.close(); done(); }; - - const session = driver.session(); - session.run('RETURN 1').then(() => { - session.close(); - driver.close(); - }); }); it('config max retry time example', done => { @@ -123,14 +118,9 @@ describe('examples', () => { // end::config-max-retry-time[] driver.onCompleted = () => { + driver.close(); done(); }; - - const session = driver.session(); - session.run('RETURN 1').then(() => { - session.close(); - driver.close(); - }); }); it('config trust example', done => { @@ -144,19 +134,9 @@ describe('examples', () => { // end::config-trust[] driver.onCompleted = () => { + driver.close(); done(); }; - - driver.onError = error => { - console.log(error); - }; - - const session = driver.session(); - session.run('RETURN 1').then(() => { - session.close(); - driver.close(); - }).catch(error => { - }); }); it('config unencrypted example', done => { @@ -169,14 +149,9 @@ describe('examples', () => { // end::config-unencrypted[] driver.onCompleted = () => { + driver.close(); done(); }; - - const session = driver.session(); - session.run('RETURN 1').then(() => { - session.close(); - driver.close(); - }); }); it('custom auth example', done => { @@ -191,14 +166,9 @@ describe('examples', () => { // end::custom-auth[] driver.onCompleted = () => { + driver.close(); done(); }; - - const session = driver.session(); - session.run('RETURN 1').then(() => { - session.close(); - driver.close(); - }); }); it('kerberos auth example', () => { @@ -239,7 +209,7 @@ describe('examples', () => { // tag::driver-lifecycle[] const driver = neo4j.driver(uri, neo4j.auth.basic(user, password)); - driver.onCompleted = metadata => { + driver.onCompleted = () => { console.log('Driver created'); }; diff --git a/test/v1/session.test.js b/test/v1/session.test.js index 8bfbf62bf..f1e37fa7d 100644 --- a/test/v1/session.test.js +++ b/test/v1/session.test.js @@ -25,24 +25,25 @@ import {SingleConnectionProvider} from '../../src/v1/internal/connection-provide import FakeConnection from '../internal/fake-connection'; import sharedNeo4j from '../internal/shared-neo4j'; import _ from 'lodash'; +import {ServerVersion, VERSION_3_1_0} from '../../src/v1/internal/server-version'; describe('session', () => { let driver; let session; - let serverMetadata; + let serverVersion; let originalTimeout; beforeEach(done => { driver = neo4j.driver('bolt://localhost', sharedNeo4j.authToken); - driver.onCompleted = meta => { - serverMetadata = meta['server']; - }; session = driver.session(); originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; - session.run('MATCH (n) DETACH DELETE n').then(done); + session.run('MATCH (n) DETACH DELETE n').then(result => { + serverVersion = ServerVersion.fromString(result.summary.server.version); + done(); + }); }); afterEach(() => { @@ -1039,9 +1040,7 @@ describe('session', () => { }); function serverIs31OrLater(done) { - // lazy way of checking the version number - // if server has been set we know it is at least 3.1 - if (!serverMetadata) { + if (serverVersion.compareTo(VERSION_3_1_0) < 0) { done(); return false; } diff --git a/test/v1/transaction.test.js b/test/v1/transaction.test.js index 586ea6545..3f14fa193 100644 --- a/test/v1/transaction.test.js +++ b/test/v1/transaction.test.js @@ -18,12 +18,13 @@ */ import neo4j from '../../src/v1'; import sharedNeo4j from '../internal/shared-neo4j'; +import {ServerVersion, VERSION_3_1_0} from '../../src/v1/internal/server-version'; describe('transaction', () => { let driver; let session; - let server; + let serverVersion; let originalTimeout; beforeEach(done => { @@ -32,12 +33,12 @@ describe('transaction', () => { jasmine.DEFAULT_TIMEOUT_INTERVAL = 40000; driver = neo4j.driver("bolt://localhost", sharedNeo4j.authToken); - driver.onCompleted = meta => { - server = meta['server']; - }; session = driver.session(); - session.run("MATCH (n) DETACH DELETE n").then(done); + session.run('MATCH (n) DETACH DELETE n').then(result => { + serverVersion = ServerVersion.fromString(result.summary.server.version); + done(); + }); }); afterEach(() => { @@ -518,10 +519,7 @@ describe('transaction', () => { } function neo4jVersionOlderThan31(done) { - //lazy way of checking the version number - //if server has been set we know it is at least - //3.1 (todo actually parse the version string) - if (!server) { + if (serverVersion.compareTo(VERSION_3_1_0) < 0) { done(); return true; } diff --git a/types/v1/driver.d.ts b/types/v1/driver.d.ts index 5d8ef9af6..d80e26bc6 100644 --- a/types/v1/driver.d.ts +++ b/types/v1/driver.d.ts @@ -20,6 +20,7 @@ import Session from "./session"; import {Parameters} from "./statement-runner"; import {Neo4jError} from "./error"; +import {ServerInfo} from "./result-summary"; declare interface AuthToken { scheme: string; @@ -60,7 +61,7 @@ declare interface Driver { close(): void; - onCompleted?: (metadata: { server: string }) => void; + onCompleted?: (serverInfo: ServerInfo) => void; onError?: (error: Neo4jError) => void; }