This is an automated email from the ASF dual-hosted git repository.

JNSimba pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris-flink-connector.git


The following commit(s) were added to refs/heads/master by this push:
     new 4a3c81fc [fix] Close Flight SQL sessions when readers finish (#719)
4a3c81fc is described below

commit 4a3c81fcaff2b21e8edcf3573e2263eec5295075
Author: wudi <[email protected]>
AuthorDate: Fri Nov 6 10:26:47 2026 +0800

    [fix] Close Flight SQL sessions when readers finish (#719)
---
 .../flink/source/reader/DorisFlightSession.java    | 135 +++++++++++++
 .../source/reader/DorisFlightValueReader.java      |   7 +-
 .../source/reader/DorisFlightSessionTest.java      | 218 +++++++++++++++++++++
 .../source/reader/DorisFlightValueReaderTest.java  |  11 +-
 .../flink-doris-connector-flink1/pom.xml           |   3 +
 .../flink-doris-connector-flink2/pom.xml           |   3 +
 flink-doris-connector/pom.xml                      |  18 +-
 7 files changed, 385 insertions(+), 10 deletions(-)

diff --git 
a/flink-doris-connector/flink-doris-connector-base/src/main/java/org/apache/doris/flink/source/reader/DorisFlightSession.java
 
b/flink-doris-connector/flink-doris-connector-base/src/main/java/org/apache/doris/flink/source/reader/DorisFlightSession.java
new file mode 100644
index 00000000..1a60cc4e
--- /dev/null
+++ 
b/flink-doris-connector/flink-doris-connector-base/src/main/java/org/apache/doris/flink/source/reader/DorisFlightSession.java
@@ -0,0 +1,135 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+package org.apache.doris.flink.source.reader;
+
+import org.apache.arrow.adbc.core.AdbcConnection;
+import org.apache.arrow.adbc.core.AdbcDatabase;
+import org.apache.arrow.adbc.core.AdbcException;
+import org.apache.arrow.adbc.driver.flightsql.FlightSqlClientWithCallOptions;
+import org.apache.arrow.adbc.driver.flightsql.FlightSqlConnection;
+import org.apache.arrow.flight.CallOption;
+import org.apache.arrow.flight.CallOptions;
+import org.apache.arrow.flight.CloseSessionRequest;
+import org.apache.arrow.flight.CloseSessionResult;
+import org.apache.arrow.flight.sql.FlightSqlClient;
+import org.apache.doris.flink.exception.DorisRuntimeException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/** Closes the server session without taking ownership of the ADBC connection. 
*/
+final class DorisFlightSession implements AutoCloseable {
+    private static final Logger LOG = 
LoggerFactory.getLogger(DorisFlightSession.class);
+
+    private final AdbcConnection connection;
+    private final FlightSqlClient client;
+    private final CallOption[] callOptions;
+    private boolean closed;
+
+    private DorisFlightSession(
+            AdbcConnection connection, FlightSqlClient client, CallOption[] 
callOptions) {
+        this.connection = connection;
+        this.client = client;
+        this.callOptions = Arrays.copyOf(callOptions, callOptions.length + 1);
+        this.callOptions[callOptions.length] = CallOptions.timeout(5, 
TimeUnit.SECONDS);
+    }
+
+    static DorisFlightSession open(AdbcDatabase database) throws AdbcException 
{
+        // shortcut: ADBC 0.14.0 hides the client; remove once its close() 
sends CloseSession.
+        Field connectionClient =
+                field(FlightSqlConnection.class, "client", 
FlightSqlClientWithCallOptions.class);
+        Field flightClient =
+                field(FlightSqlClientWithCallOptions.class, "client", 
FlightSqlClient.class);
+        Field connectionOptions =
+                field(
+                        FlightSqlClientWithCallOptions.class,
+                        "connectionOptions",
+                        CallOption[].class);
+
+        // Validate the driver layout before authentication creates a server 
session.
+        AdbcConnection connection = database.connect();
+        try {
+            Object wrapper = connectionClient.get(connection);
+            FlightSqlClient client =
+                    Objects.requireNonNull((FlightSqlClient) 
flightClient.get(wrapper));
+            CallOption[] options =
+                    Objects.requireNonNull((CallOption[]) 
connectionOptions.get(wrapper));
+            return new DorisFlightSession(connection, client, options);
+        } catch (ReflectiveOperationException | RuntimeException failure) {
+            try {
+                connection.close();
+            } catch (Exception closeFailure) {
+                failure.addSuppressed(closeFailure);
+            }
+            throw new DorisRuntimeException("Cannot bind ADBC Flight SQL 
session", failure);
+        }
+    }
+
+    private static Field field(Class<?> owner, String name, Class<?> type) {
+        try {
+            Field field = owner.getDeclaredField(name);
+            if (field.getType() != type) {
+                throw new NoSuchFieldException(
+                        owner.getSimpleName() + "." + name + " has changed 
type");
+            }
+            field.setAccessible(true);
+            return field;
+        } catch (ReflectiveOperationException | RuntimeException failure) {
+            throw new DorisRuntimeException(
+                    "Unsupported ADBC Flight SQL driver: cannot access session 
for CloseSession",
+                    failure);
+        }
+    }
+
+    AdbcConnection getConnection() {
+        return connection;
+    }
+
+    public synchronized void closeSession() {
+        if (closed) {
+            return;
+        }
+        closed = true;
+        // Remote session cleanup must not turn a completed read into a 
failure.
+        try {
+            CloseSessionResult.Status status =
+                    client.closeSession(new CloseSessionRequest(), 
callOptions).getStatus();
+            if (status != CloseSessionResult.Status.CLOSED
+                    && status != CloseSessionResult.Status.CLOSING) {
+                LOG.warn(
+                        "Doris Flight SQL session was not closed, status: {}; 
it may remain open"
+                                + " until the server idle timeout",
+                        status);
+            }
+        } catch (Exception e) {
+            LOG.warn(
+                    "Failed to close Doris Flight SQL session; it may remain 
open until the server"
+                            + " idle timeout",
+                    e);
+        }
+    }
+
+    @Override
+    public void close() {
+        closeSession();
+    }
+}
diff --git 
a/flink-doris-connector/flink-doris-connector-base/src/main/java/org/apache/doris/flink/source/reader/DorisFlightValueReader.java
 
b/flink-doris-connector/flink-doris-connector-base/src/main/java/org/apache/doris/flink/source/reader/DorisFlightValueReader.java
index 9856db53..f553480c 100644
--- 
a/flink-doris-connector/flink-doris-connector-base/src/main/java/org/apache/doris/flink/source/reader/DorisFlightValueReader.java
+++ 
b/flink-doris-connector/flink-doris-connector-base/src/main/java/org/apache/doris/flink/source/reader/DorisFlightValueReader.java
@@ -71,6 +71,7 @@ public class DorisFlightValueReader extends ValueReader 
implements AutoCloseable
     protected AdbcConnection client;
     private RootAllocator allocator;
     private AdbcDatabase database;
+    private DorisFlightSession session;
     protected Lock clientLock = new ReentrantLock();
 
     private final DorisSourceSplit split;
@@ -274,7 +275,8 @@ public class DorisFlightValueReader extends ValueReader 
implements AutoCloseable
                     createConnectionParameters(
                             String.valueOf(split[0]), flightSqlPort, options, 
rootCertificates);
             database = driver.open(parameters);
-            return database.connect();
+            session = DorisFlightSession.open(database);
+            return session.getConnection();
         } catch (AdbcException | IOException e) {
             LOG.debug("Open Flight Connection error: {}", e.getMessage());
             throw new RuntimeException(e);
@@ -415,10 +417,11 @@ public class DorisFlightValueReader extends ValueReader 
implements AutoCloseable
 
     private void closeFlightResources() throws Exception {
         try {
-            closeAll(arrowReader, statement, client, database, allocator);
+            closeAll(arrowReader, statement, session, client, database, 
allocator);
         } finally {
             arrowReader = null;
             statement = null;
+            session = null;
             client = null;
             database = null;
             allocator = null;
diff --git 
a/flink-doris-connector/flink-doris-connector-base/src/test/java/org/apache/doris/flink/source/reader/DorisFlightSessionTest.java
 
b/flink-doris-connector/flink-doris-connector-base/src/test/java/org/apache/doris/flink/source/reader/DorisFlightSessionTest.java
new file mode 100644
index 00000000..81be277b
--- /dev/null
+++ 
b/flink-doris-connector/flink-doris-connector-base/src/test/java/org/apache/doris/flink/source/reader/DorisFlightSessionTest.java
@@ -0,0 +1,218 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+package org.apache.doris.flink.source.reader;
+
+import org.apache.arrow.adbc.core.AdbcConnection;
+import org.apache.arrow.adbc.core.AdbcDatabase;
+import org.apache.arrow.adbc.core.AdbcDriver;
+import org.apache.arrow.adbc.driver.flightsql.FlightSqlDriver;
+import org.apache.arrow.flight.CallHeaders;
+import org.apache.arrow.flight.CallStatus;
+import org.apache.arrow.flight.CloseSessionRequest;
+import org.apache.arrow.flight.CloseSessionResult;
+import org.apache.arrow.flight.FlightServer;
+import org.apache.arrow.flight.Location;
+import org.apache.arrow.flight.auth2.CallHeaderAuthenticator;
+import org.apache.arrow.flight.sql.NoOpFlightSqlProducer;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.doris.flink.exception.DorisRuntimeException;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class DorisFlightSessionTest {
+    @Test
+    void closesTheSessionSharedWithAdbcOnlyOnce() throws Exception {
+        SessionProducer producer = new SessionProducer();
+        try (RootAllocator allocator = new RootAllocator();
+                FlightServer server = server(allocator, producer)) {
+            Map<String, Object> parameters = parameters(server);
+            try (AdbcDatabase database = new 
FlightSqlDriver(allocator).open(parameters)) {
+                DorisFlightSession session = DorisFlightSession.open(database);
+                try (AdbcConnection connection = session.getConnection();
+                        DorisFlightSession closingSession = session) {
+                    assertThat(producer.opens.get()).isEqualTo(1);
+                    assertThat(parameters)
+                            .containsEntry(AdbcDriver.PARAM_USERNAME.getKey(), 
"root")
+                            .containsEntry(AdbcDriver.PARAM_PASSWORD.getKey(), 
"password");
+                    closingSession.closeSession();
+                    closingSession.closeSession();
+                    assertThat(producer.closes.get()).isEqualTo(1);
+                    assertThat(producer.bearerCalls.get()).isEqualTo(1);
+                    assertThat(producer.active.get()).isZero();
+                }
+            }
+            assertThat(producer.closes.get()).isEqualTo(1);
+        }
+    }
+
+    @Test
+    void releasesAdbcResourcesEvenWhenCloseSessionFails() throws Exception {
+        SessionProducer producer = new SessionProducer();
+        producer.rejectClose = true;
+        try (RootAllocator allocator = new RootAllocator();
+                FlightServer server = server(allocator, producer);
+                AdbcDatabase database = new 
FlightSqlDriver(allocator).open(parameters(server))) {
+            DorisFlightSession session = DorisFlightSession.open(database);
+            assertThatCode(() -> DorisFlightValueReader.closeAll(session, 
session.getConnection()))
+                    .doesNotThrowAnyException();
+            session.close();
+            assertThat(producer.closes.get()).isEqualTo(1);
+        }
+    }
+
+    @Test
+    void boundsUnresponsiveSessionCloseAndContinuesCleanup() throws Exception {
+        SessionProducer producer = new SessionProducer();
+        producer.hangClose = true;
+        try (RootAllocator allocator = new RootAllocator();
+                FlightServer server = server(allocator, producer);
+                AdbcDatabase database = new 
FlightSqlDriver(allocator).open(parameters(server))) {
+            DorisFlightSession session = DorisFlightSession.open(database);
+            long start = System.nanoTime();
+            assertThatCode(() -> DorisFlightValueReader.closeAll(session, 
session.getConnection()))
+                    .doesNotThrowAnyException();
+            assertThat(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - 
start))
+                    .isBetween(4000L, 10000L);
+            session.close();
+            assertThat(producer.closes.get()).isEqualTo(1);
+        }
+    }
+
+    @Test
+    void closesConnectionWhenSessionBindingFails() throws Exception {
+        AdbcDatabase database = mock(AdbcDatabase.class);
+        AdbcConnection connection = mock(AdbcConnection.class);
+        when(database.connect()).thenReturn(connection);
+
+        assertThatThrownBy(() -> DorisFlightSession.open(database))
+                .isInstanceOf(DorisRuntimeException.class)
+                .hasMessageContaining("Cannot bind ADBC Flight SQL session");
+        verify(connection).close();
+    }
+
+    @Test
+    void acceptsSessionCloseInProgress() throws Exception {
+        SessionProducer producer = new SessionProducer();
+        producer.closeStatus = CloseSessionResult.Status.CLOSING;
+        try (RootAllocator allocator = new RootAllocator();
+                FlightServer server = server(allocator, producer);
+                AdbcDatabase database = new 
FlightSqlDriver(allocator).open(parameters(server))) {
+            DorisFlightSession session = DorisFlightSession.open(database);
+            DorisFlightValueReader.closeAll(session, session.getConnection());
+            assertThat(producer.closes.get()).isEqualTo(1);
+        }
+    }
+
+    @Test
+    void continuesCleanupWhenTheServerCannotCloseSession() throws Exception {
+        SessionProducer producer = new SessionProducer();
+        producer.closeStatus = CloseSessionResult.Status.NOT_CLOSABLE;
+        try (RootAllocator allocator = new RootAllocator();
+                FlightServer server = server(allocator, producer);
+                AdbcDatabase database = new 
FlightSqlDriver(allocator).open(parameters(server))) {
+            DorisFlightSession session = DorisFlightSession.open(database);
+            assertThatCode(() -> DorisFlightValueReader.closeAll(session, 
session.getConnection()))
+                    .doesNotThrowAnyException();
+            assertThat(producer.closes.get()).isEqualTo(1);
+            assertThat(producer.active.get()).isEqualTo(1);
+        }
+    }
+
+    private FlightServer server(RootAllocator allocator, SessionProducer 
producer)
+            throws Exception {
+        return FlightServer.builder(allocator, 
Location.forGrpcInsecure("127.0.0.1", 0), producer)
+                .headerAuthenticator(producer::authenticate)
+                .build()
+                .start();
+    }
+
+    private Map<String, Object> parameters(FlightServer server) {
+        Map<String, Object> parameters = new HashMap<>();
+        AdbcDriver.PARAM_URI.set(
+                parameters,
+                Location.forGrpcInsecure("127.0.0.1", 
server.getPort()).getUri().toString());
+        AdbcDriver.PARAM_USERNAME.set(parameters, "root");
+        AdbcDriver.PARAM_PASSWORD.set(parameters, "password");
+        return parameters;
+    }
+
+    private static class SessionProducer extends NoOpFlightSqlProducer {
+        private final AtomicInteger opens = new AtomicInteger();
+        private final AtomicInteger active = new AtomicInteger();
+        private final AtomicInteger closes = new AtomicInteger();
+        private final AtomicInteger bearerCalls = new AtomicInteger();
+        private boolean rejectClose;
+        private boolean hangClose;
+        private CloseSessionResult.Status closeStatus = 
CloseSessionResult.Status.CLOSED;
+
+        private CallHeaderAuthenticator.AuthResult authenticate(CallHeaders 
headers) {
+            String authorization = headers.get("authorization");
+            if ("Basic cm9vdDpwYXNzd29yZA==".equals(authorization)) {
+                opens.incrementAndGet();
+                active.incrementAndGet();
+            } else if ("Bearer original-session".equals(authorization) && 
active.get() == 1) {
+                bearerCalls.incrementAndGet();
+            } else {
+                throw CallStatus.UNAUTHENTICATED.toRuntimeException();
+            }
+            return new CallHeaderAuthenticator.AuthResult() {
+                @Override
+                public String getPeerIdentity() {
+                    return "original-session";
+                }
+
+                @Override
+                public void appendToOutgoingHeaders(CallHeaders outgoing) {
+                    outgoing.insert("authorization", "Bearer 
original-session");
+                }
+            };
+        }
+
+        @Override
+        public void closeSession(
+                CloseSessionRequest request,
+                CallContext context,
+                StreamListener<CloseSessionResult> listener) {
+            closes.incrementAndGet();
+            if (hangClose) {
+                return;
+            }
+            if (rejectClose) {
+                listener.onError(
+                        CallStatus.INTERNAL.withDescription("close 
refused").toRuntimeException());
+                return;
+            }
+            if (closeStatus == CloseSessionResult.Status.CLOSED) {
+                active.decrementAndGet();
+            }
+            listener.onNext(new CloseSessionResult(closeStatus));
+            listener.onCompleted();
+        }
+    }
+}
diff --git 
a/flink-doris-connector/flink-doris-connector-base/src/test/java/org/apache/doris/flink/source/reader/DorisFlightValueReaderTest.java
 
b/flink-doris-connector/flink-doris-connector-base/src/test/java/org/apache/doris/flink/source/reader/DorisFlightValueReaderTest.java
index 059714d6..23663cad 100644
--- 
a/flink-doris-connector/flink-doris-connector-base/src/test/java/org/apache/doris/flink/source/reader/DorisFlightValueReaderTest.java
+++ 
b/flink-doris-connector/flink-doris-connector-base/src/test/java/org/apache/doris/flink/source/reader/DorisFlightValueReaderTest.java
@@ -108,10 +108,14 @@ class DorisFlightValueReaderTest {
         DorisReadOptions readOptions = 
DorisReadOptions.builder().setFlightSqlPort(8815).build();
         AdbcDatabase database = mock(AdbcDatabase.class);
         AdbcConnection connection = mock(AdbcConnection.class);
-        when(database.connect()).thenReturn(connection);
+        DorisFlightSession session = mock(DorisFlightSession.class);
+        when(session.getConnection()).thenReturn(connection);
         when(connection.createStatement()).thenThrow(new 
RuntimeException("create failed"));
+        RuntimeException closeFailure = new RuntimeException("close connection 
failed");
+        doThrow(closeFailure).when(connection).close();
 
         try (MockedStatic<RestService> restService = 
mockStatic(RestService.class);
+                MockedStatic<DorisFlightSession> sessions = 
mockStatic(DorisFlightSession.class);
                 MockedConstruction<RootAllocator> allocators =
                         mockConstruction(RootAllocator.class);
                 MockedConstruction<FlightSqlDriver> drivers =
@@ -119,6 +123,7 @@ class DorisFlightValueReaderTest {
                                 FlightSqlDriver.class,
                                 (driver, context) ->
                                         
when(driver.open(anyMap())).thenReturn(database))) {
+            sessions.when(() -> 
DorisFlightSession.open(database)).thenReturn(session);
             restService
                     .when(() -> RestService.getSchema(eq(options), 
eq(readOptions), any()))
                     .thenReturn(new Schema());
@@ -128,8 +133,10 @@ class DorisFlightValueReaderTest {
 
             assertThatThrownBy(() -> new DorisFlightValueReader(split, 
options, readOptions))
                     .isInstanceOf(RuntimeException.class)
-                    .hasMessage("create failed");
+                    .hasMessage("create failed")
+                    .hasSuppressedException(closeFailure);
 
+            verify(session).close();
             verify(connection).close();
             verify(database).close();
             verify(allocators.constructed().get(0)).close();
diff --git a/flink-doris-connector/flink-doris-connector-flink1/pom.xml 
b/flink-doris-connector/flink-doris-connector-flink1/pom.xml
index 95e3835c..6d92937e 100644
--- a/flink-doris-connector/flink-doris-connector-flink1/pom.xml
+++ b/flink-doris-connector/flink-doris-connector-flink1/pom.xml
@@ -229,6 +229,9 @@ under the License.
                 <artifactId>maven-shade-plugin</artifactId>
                 <version>3.4.1</version>
                 <configuration>
+                    <transformers>
+                        <transformer 
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
+                    </transformers>
                     <relocations>
                         <relocation>
                             <pattern>org.apache.arrow</pattern>
diff --git a/flink-doris-connector/flink-doris-connector-flink2/pom.xml 
b/flink-doris-connector/flink-doris-connector-flink2/pom.xml
index 0343790a..1dc27563 100644
--- a/flink-doris-connector/flink-doris-connector-flink2/pom.xml
+++ b/flink-doris-connector/flink-doris-connector-flink2/pom.xml
@@ -144,6 +144,9 @@ under the License.
                 <artifactId>maven-shade-plugin</artifactId>
                 <version>3.4.1</version>
                 <configuration>
+                    <transformers>
+                        <transformer 
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
+                    </transformers>
                     <relocations>
                         <relocation>
                             <pattern>org.apache.arrow</pattern>
diff --git a/flink-doris-connector/pom.xml b/flink-doris-connector/pom.xml
index d672c1fa..5c2196ff 100644
--- a/flink-doris-connector/pom.xml
+++ b/flink-doris-connector/pom.xml
@@ -97,8 +97,9 @@ under the License.
         <mysql.driver.version>8.0.26</mysql.driver.version>
         <ojdbc.version>19.3.0.0</ojdbc.version>
         <log4j.version>2.17.1</log4j.version>
-        <arrow.version>15.0.2</arrow.version>
-        <adbc.version>0.12.0</adbc.version>
+        <arrow.version>17.0.0</arrow.version>
+        <adbc.version>0.14.0</adbc.version>
+        <protobuf.version>3.25.1</protobuf.version>
         <awssdk.version>2.29.52</awssdk.version>
     </properties>
 
@@ -176,13 +177,18 @@ under the License.
             </dependency>
             <dependency>
                 <groupId>org.apache.arrow</groupId>
-                <artifactId>arrow-vector</artifactId>
+                <artifactId>arrow-bom</artifactId>
                 <version>${arrow.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
             </dependency>
+            <!-- Align protobuf runtime and utilities used by ADBC and Flight. 
-->
             <dependency>
-                <groupId>org.apache.arrow</groupId>
-                <artifactId>arrow-memory-netty</artifactId>
-                <version>${arrow.version}</version>
+                <groupId>com.google.protobuf</groupId>
+                <artifactId>protobuf-bom</artifactId>
+                <version>${protobuf.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
             </dependency>
             <!--  jackson  -->
             <dependency>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to