dblevins    2005/08/23 16:10:37

  Modified:    modules/core/src/java/org/openejb/resource/jdbc
                        JdbcConnectionFactory.java
                        JdbcManagedConnection.java
  Log:

  Fixed exception handling and reworked code.
  
  Revision  Changes    Path
  1.3       +92 -83    
openejb1/modules/core/src/java/org/openejb/resource/jdbc/JdbcConnectionFactory.java
  
  Index: JdbcConnectionFactory.java
  ===================================================================
  RCS file: 
/home/projects/openejb/scm/openejb1/modules/core/src/java/org/openejb/resource/jdbc/JdbcConnectionFactory.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- JdbcConnectionFactory.java        23 Aug 2005 04:12:53 -0000      1.2
  +++ JdbcConnectionFactory.java        23 Aug 2005 20:10:37 -0000      1.3
  @@ -44,114 +44,123 @@
    */
   package org.openejb.resource.jdbc;
   
  -import java.sql.SQLException;
  -
  +import javax.naming.Reference;
   import javax.resource.ResourceException;
  +import javax.resource.spi.ApplicationServerInternalException;
   import javax.resource.spi.ConnectionManager;
   import javax.resource.spi.ManagedConnectionFactory;
  +import javax.resource.spi.ResourceAdapterInternalException;
  +import javax.resource.spi.ResourceAllocationException;
  +import java.io.PrintWriter;
  +import java.sql.Connection;
  +import java.sql.SQLException;
   
  -/*
  -* As a connection factory the JdbcConnecitonFactory must implement the 
Serializable and 
  -* Referenceable methods so that it can be store in a JNDI name space.  The 
referenc itself
  -* is an application specific object that can be used to lookup and configure 
a new ManagedConnectionFactory
  -* the JdbcConnecitonFactory is only a store for this reference, its not 
expected to be functional after 
  -* it has been serialized into a JNDI name space.  See section 10.5.3 of the 
Connector API spec.
  -*/
  -public class JdbcConnectionFactory 
  -implements 
  -javax.sql.DataSource, 
  -javax.resource.Referenceable, 
  -java.io.Serializable {
  +/**
  + * As a connection factory the JdbcConnecitonFactory must implement the
  + * Serializable and Referenceable methods so that it can be store in a
  + * JNDI name space.  The reference itself is an application specific object
  + * that can be used to lookup and configure a new ManagedConnectionFactory
  + * the JdbcConnecitonFactory is only a store for this reference, its not
  + * expected to be functional after it has been serialized into a JNDI
  + * namespace.
  + * <p/>
  + * See section 10.5.3 of the J2EE Connector Architecture 1.0 spec.
  + */
  +public class JdbcConnectionFactory implements javax.sql.DataSource, 
javax.resource.Referenceable, java.io.Serializable {
  +    /**
  +     * A Reference to this ConnectionFactory in JNDI
  +     */
  +    private Reference jndiReference;
       
  -    protected transient ManagedConnectionFactory mngdCxFactory;
  -    protected transient ConnectionManager cxManager;
  -    protected transient java.io.PrintWriter logWriter;
  -    protected int logTimeout = 0;
  -    
  -    // Reference to this ConnectionFactory
  -    javax.naming.Reference jndiReference;
  +    private final transient ManagedConnectionFactory 
managedConnectionFactory;
  +    private final transient ConnectionManager connectionManager;
       private final String jdbcUrl;
       private final String jdbcDriver;
       private final String defaultPassword;
       private final String defaultUserName;
  +    private transient PrintWriter logWriter;
  +    private int logTimeout = 0;
   
  -    // setReference is called by deployment code
  -    public void setReference(javax.naming.Reference ref) {
  -        jndiReference = ref;
  -    }
  -    // getReference is called by JNDI provider during Context.bind
  -    public javax.naming.Reference getReference() {
  -        return jndiReference;
  -    }
  -    
  -    public JdbcConnectionFactory(ManagedConnectionFactory mngdCxFactory, 
ConnectionManager cxManager, String jdbcUrl, String jdbcDriver, String 
defaultPassword, String defaultUserName)
  -    throws ResourceException{
  -        this.mngdCxFactory = mngdCxFactory;
  -        this.cxManager = cxManager;
  -        this.logWriter = mngdCxFactory.getLogWriter();
  +    public JdbcConnectionFactory(ManagedConnectionFactory 
managedConnectionFactory,
  +                                 ConnectionManager connectionManager, String 
jdbcUrl,
  +                                 String jdbcDriver, String defaultPassword, 
String defaultUserName) throws ResourceException {
  +        this.managedConnectionFactory = managedConnectionFactory;
  +        this.connectionManager = connectionManager;
  +        this.logWriter = managedConnectionFactory.getLogWriter();
           this.jdbcUrl = jdbcUrl;
           this.jdbcDriver = jdbcDriver;
           this.defaultPassword = defaultPassword;
           this.defaultUserName = defaultUserName;
       }
  -        
  -    public java.sql.Connection getConnection() throws SQLException{
  +
  +    /**
  +     * setReference is called by deployment code
  +     *
  +     * @param jndiReference
  +     */
  +    public void setReference(Reference jndiReference) {
  +        this.jndiReference = jndiReference;
  +    }
  +
  +    /**
  +     * getReference is called by JNDI provider during Context.bind
  +     *
  +     * @return
  +     */
  +    public Reference getReference() {
  +        return jndiReference;
  +    }
  +
  +    public Connection getConnection() throws SQLException {
           return getConnection(defaultUserName, defaultPassword);
       }
   
  -    public java.sql.Connection getConnection(java.lang.String username, 
java.lang.String password)throws SQLException{
  +    public Connection getConnection(java.lang.String username, 
java.lang.String password) throws SQLException {
           return getConnection(new JdbcConnectionRequestInfo(username, 
password, jdbcDriver, jdbcUrl));
       }
  -    protected java.sql.Connection getConnection(JdbcConnectionRequestInfo 
conInfo) throws SQLException{
  -        try{
  -            // FIXME: Use ManagedConnection.assocoate() method here if the 
client has already obtained a physical connection.
  -            // the previous connection is either shared or invalidated. IT 
should probably be shared.
  -            return 
(java.sql.Connection)cxManager.allocateConnection(mngdCxFactory, conInfo);
  -        }catch(javax.resource.spi.ApplicationServerInternalException asi){
  -            // Application problem with the ConnectionManager. May be a 
SQLException
  -            if(asi.getLinkedException() instanceof SQLException)
  -                throw (SQLException)asi.getLinkedException();
  -            else
  -                throw new SQLException("Error code: 
"+asi.getErrorCode()+"\nApplication error in 
ContainerManager"+((asi.getLinkedException()!=null)?asi.getLinkedException().getMessage():""));
  -        }catch(javax.resource.spi.SecurityException se){
  -            // The username/password in the conInfo is invalid. Should be a 
nested SQLException.
  -            if(se.getLinkedException() instanceof SQLException)
  -                throw (SQLException)se.getLinkedException();
  -            else
  -                throw new SQLException("Error code: 
"+se.getErrorCode()+"\nAuthentication error. Invalid 
credentials"+((se.getLinkedException()!=null)?se.getLinkedException().getMessage():""));
  -        }catch(javax.resource.spi.ResourceAdapterInternalException rai){
  -            // some kind of connection problem. Should be a nested 
SQLException.
  -            if(rai.getLinkedException() instanceof SQLException)
  -                throw (SQLException)rai.getLinkedException();
  -            else
  -                throw new SQLException("Error code: 
"+rai.getErrorCode()+"\nJDBC Connection 
problem"+((rai.getLinkedException()!=null)?rai.getLinkedException().getMessage():""));
  -        }catch(javax.resource.spi.ResourceAllocationException rae){
  -            // a connection could not be obtained from the driver or 
ConnectionManager.  May be a SQLException
  -            if(rae.getLinkedException() instanceof SQLException)
  -                throw (SQLException)rae.getLinkedException();
  -            else
  -                throw new SQLException("Error code: 
"+rae.getErrorCode()+"\nJDBC Connection could not be 
obtained"+((rae.getLinkedException()!=null)?rae.getLinkedException().getMessage():""));
  -        }catch(javax.resource.ResourceException re){
  -            // Unknown cause of exception.  May be a SQLException
  -            if(re.getLinkedException() instanceof SQLException)
  -                throw (SQLException)re.getLinkedException();
  -            else
  -                throw new SQLException("Error code: 
"+re.getErrorCode()+"\nJDBC Connection Factory 
problem"+((re.getLinkedException()!=null)?re.getLinkedException().getMessage():""));
  +
  +    protected Connection getConnection(JdbcConnectionRequestInfo 
connectionRequestInfo) throws SQLException {
  +        // TODO: Use ManagedConnection.assocoate() method here if the client 
has already obtained a physical connection.
  +        // the previous connection is either shared or invalidated. IT 
should probably be shared.
  +        try {
  +            return (Connection) 
connectionManager.allocateConnection(managedConnectionFactory, 
connectionRequestInfo);
  +        } catch (ApplicationServerInternalException e) {
  +            throw convertToSQLException(e, "Application error in 
ContainerManager");
  +        } catch (javax.resource.spi.SecurityException e) {
  +            throw convertToSQLException(e, "Authentication error. Invalid 
credentials");
  +        } catch (ResourceAdapterInternalException e) {
  +            throw convertToSQLException(e, "JDBC Connection problem");
  +        } catch (ResourceAllocationException e) {
  +            throw convertToSQLException(e, "JDBC Connection could not be 
obtained");
  +        } catch (ResourceException e) {
  +            throw convertToSQLException(e, "JDBC Connection Factory 
problem");
  +        }
  +    }
  +
  +    private SQLException convertToSQLException(ResourceException e, String 
error) {
  +        Throwable cause = e.getCause();
  +        if (cause instanceof SQLException) {
  +            return (SQLException) cause;
  +        } else {
  +            String message = ((cause != null) ? cause.getMessage() : "");
  +            return (SQLException) new SQLException("Error code: " + 
e.getErrorCode() + error + message).initCause(e);
           }
       }
  -    public int getLoginTimeout(){
  +
  +    public int getLoginTimeout() {
           return logTimeout;
  -    } 
  -       
  -    public java.io.PrintWriter getLogWriter(){
  +    }
  +
  +    public java.io.PrintWriter getLogWriter() {
           return logWriter;
       }
  -    public void setLoginTimeout(int seconds){
  -        //FIXME: how should log timeout work?
  +
  +    public void setLoginTimeout(int seconds) {
  +        //TODO: how should log timeout work?
           logTimeout = seconds;
  -    } 
  -       
  -    public void setLogWriter(java.io.PrintWriter out){
  +    }
  +
  +    public void setLogWriter(java.io.PrintWriter out) {
           logWriter = out;
       }
   }
  
  
  
  1.4       +92 -96    
openejb1/modules/core/src/java/org/openejb/resource/jdbc/JdbcManagedConnection.java
  
  Index: JdbcManagedConnection.java
  ===================================================================
  RCS file: 
/home/projects/openejb/scm/openejb1/modules/core/src/java/org/openejb/resource/jdbc/JdbcManagedConnection.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- JdbcManagedConnection.java        23 Aug 2005 04:12:53 -0000      1.3
  +++ JdbcManagedConnection.java        23 Aug 2005 20:10:37 -0000      1.4
  @@ -44,34 +44,34 @@
    */
   package org.openejb.resource.jdbc;
   
  -import java.util.HashSet;
  -import java.util.Set;
  -
  +import javax.resource.ResourceException;
   import javax.resource.spi.ConnectionEvent;
   import javax.resource.spi.ConnectionEventListener;
   import javax.resource.spi.ConnectionRequestInfo;
   import javax.resource.spi.ManagedConnection;
  -import javax.resource.spi.ManagedConnectionMetaData;
   import javax.resource.spi.ManagedConnectionFactory;
  -import javax.resource.ResourceException;
  +import javax.resource.spi.ManagedConnectionMetaData;
  +import java.io.PrintWriter;
  +import java.sql.Connection;
  +import java.util.ArrayList;
  +import java.util.HashSet;
  +import java.util.List;
  +import java.util.Set;
   
   public class JdbcManagedConnection implements ManagedConnection {
   
  -    private ManagedConnectionFactory managedFactory;
  -    private java.sql.Connection sqlConn;
  -    private JdbcConnectionRequestInfo requestInfo;
  -    private JdbcManagedConnectionMetaData metaData;
  -
  -    // there may be many conneciton handles active at any one time
  -    private java.util.Vector jdbcConnections = new java.util.Vector();
  -    private Set listeners;
  -    private java.io.PrintWriter logWriter;
  -    private JdbcLocalTransaction localTransaction;
  +    private final JdbcConnectionRequestInfo requestInfo;
  +    private final JdbcManagedConnectionMetaData metaData;
  +    private final JdbcLocalTransaction localTransaction;
  +    private final List jdbcConnections = new ArrayList();
  +    private final Set listeners;
  +
  +    private Connection sqlConn;
  +    private PrintWriter logWriter;
   
       public JdbcManagedConnection(ManagedConnectionFactory managedFactory, 
java.sql.Connection sqlConn, JdbcConnectionRequestInfo rxInfo)
  -    throws javax.resource.spi.ResourceAdapterInternalException {
  +            throws javax.resource.spi.ResourceAdapterInternalException {
           listeners = java.util.Collections.synchronizedSet(new HashSet());
  -        this.managedFactory = managedFactory;
           this.requestInfo = rxInfo;
           this.sqlConn = sqlConn;
           try {
  @@ -79,59 +79,57 @@
           } catch (ResourceException e) {
               throw new RuntimeException(e);
           }
  -        try{
  -        metaData = new JdbcManagedConnectionMetaData(sqlConn.getMetaData());
  -        }catch(java.sql.SQLException sqlE){
  +        try {
  +            metaData = new 
JdbcManagedConnectionMetaData(sqlConn.getMetaData());
  +        } catch (java.sql.SQLException sqlE) {
               throw new 
javax.resource.spi.ResourceAdapterInternalException("Problem while attempting 
to access meta data from physical connection", ErrorCode.JDBC_0004);
           }
           localTransaction = new JdbcLocalTransaction(this);
       }
   
  -    protected java.sql.Connection getSQLConnection(){
  +    protected java.sql.Connection getSQLConnection() {
           return sqlConn;
       }
   
  -    protected JdbcConnectionRequestInfo getRequestInfo(){
  +    protected JdbcConnectionRequestInfo getRequestInfo() {
           return requestInfo;
       }
   
  -    public void addConnectionEventListener(ConnectionEventListener listener) 
 {
  +    public void addConnectionEventListener(ConnectionEventListener listener) 
{
           listeners.add(listener);
       }
   
  -    public void associateConnection(java.lang.Object connection)  throws 
javax.resource.ResourceException {
  -        if(connection instanceof JdbcConnection){
  -            JdbcConnection jdbcConn = (JdbcConnection)connection;
  +    public void associateConnection(java.lang.Object connection) throws 
javax.resource.ResourceException {
  +        if (connection instanceof JdbcConnection) {
  +            JdbcConnection jdbcConn = (JdbcConnection) connection;
               jdbcConn.associate(this);
  -        }else{
  +        } else {
               throw new javax.resource.ResourceException("Connection object is 
the wrong type. It must be an instance of JdbcConnection");
           }
       }
   
       /**
  -    * This method will invalidate any JdbcConnection handles that have not 
already been invalidated (they self invalidate when they are explicitly closed).
  -    */
  -    public void cleanup()  throws javax.resource.ResourceException {
  -         synchronized(jdbcConnections)
  -         {
  -             Object [] connectionHandles = jdbcConnections.toArray();
  -             for(int i = 0; i < connectionHandles.length; i++){
  -                 JdbcConnection handle = 
(JdbcConnection)connectionHandles[i];
  -                 handle.invalidate();
  -             }
  -             jdbcConnections.clear();
  -             localTransaction.cleanup();
  -             }
  +     * This method will invalidate any JdbcConnection handles that have not 
already been invalidated (they self invalidate when they are explicitly closed).
  +     */
  +    public void cleanup() throws javax.resource.ResourceException {
  +        synchronized (jdbcConnections) {
  +            Object[] connectionHandles = jdbcConnections.toArray();
  +            for (int i = 0; i < connectionHandles.length; i++) {
  +                JdbcConnection handle = (JdbcConnection) 
connectionHandles[i];
  +                handle.invalidate();
  +            }
  +            jdbcConnections.clear();
  +            localTransaction.cleanup();
  +        }
       }
   
  -    public void destroy()  throws javax.resource.ResourceException {
  +    public void destroy() throws javax.resource.ResourceException {
           cleanup();
  -        try{
  -        sqlConn.close();
  -        }catch(java.sql.SQLException sqlE){
  +        try {
  +            sqlConn.close();
  +        } catch (java.sql.SQLException sqlE) {
               throw new 
javax.resource.spi.ResourceAdapterInternalException("Problem attempting to 
close physical JDBC connection", ErrorCode.JDBC_0003);
           }
  -        managedFactory = null;
           sqlConn = null;
           listeners.clear();
       }
  @@ -141,108 +139,106 @@
       * which implements the java.sql.Connection interface and wrappers the 
physical JDBC connection.
       *
       */
  -    public java.lang.Object getConnection(javax.security.auth.Subject 
subject,ConnectionRequestInfo cxRequestInfo)  throws 
javax.resource.ResourceException  {
  -         synchronized(jdbcConnections)
  -         {
  -             JdbcConnection jdbcCon = new JdbcConnection(this,sqlConn);
  -             jdbcConnections.add(jdbcCon);
  -             return jdbcCon;
  -             }
  +    public java.lang.Object getConnection(javax.security.auth.Subject 
subject, ConnectionRequestInfo cxRequestInfo) throws 
javax.resource.ResourceException {
  +        synchronized (jdbcConnections) {
  +            JdbcConnection jdbcCon = new JdbcConnection(this, sqlConn);
  +            jdbcConnections.add(jdbcCon);
  +            return jdbcCon;
  +        }
       }
   
  -    public javax.resource.spi.LocalTransaction getLocalTransaction()  throws 
javax.resource.ResourceException  {
  +    public javax.resource.spi.LocalTransaction getLocalTransaction() throws 
javax.resource.ResourceException {
           return localTransaction;
       }
   
  -    public java.io.PrintWriter getLogWriter()  throws 
javax.resource.ResourceException  {
  +    public java.io.PrintWriter getLogWriter() throws 
javax.resource.ResourceException {
           return logWriter;
       }
   
  -    public ManagedConnectionMetaData getMetaData()  throws 
javax.resource.ResourceException  {
  +    public ManagedConnectionMetaData getMetaData() throws 
javax.resource.ResourceException {
           return metaData;
       }
   
  -    public javax.transaction.xa.XAResource getXAResource()  throws 
javax.resource.ResourceException  {
  +    public javax.transaction.xa.XAResource getXAResource() throws 
javax.resource.ResourceException {
           throw new javax.resource.NotSupportedException("Method not 
implemented");
       }
   
  -    public void removeConnectionEventListener(ConnectionEventListener 
listener)    {
  +    public void removeConnectionEventListener(ConnectionEventListener 
listener) {
           listeners.remove(listener);
       }
   
  -    public void setLogWriter(java.io.PrintWriter out)  throws 
javax.resource.ResourceException {
  +    public void setLogWriter(java.io.PrintWriter out) throws 
javax.resource.ResourceException {
           logWriter = out;
       }
   
  -    protected void localTransactionCommitted(){
  +    protected void localTransactionCommitted() {
           ConnectionEvent event = new ConnectionEvent(this, 
ConnectionEvent.LOCAL_TRANSACTION_COMMITTED);
  -        Object [] elements = listeners.toArray();
  -        for(int i = 0; i < elements.length; i++){
  -            ConnectionEventListener eventListener = 
(ConnectionEventListener)elements[i];
  +        Object[] elements = listeners.toArray();
  +        for (int i = 0; i < elements.length; i++) {
  +            ConnectionEventListener eventListener = 
(ConnectionEventListener) elements[i];
               eventListener.localTransactionCommitted(event);
           }
       }
   
  -    protected void localTransactionRolledback(){
  +    protected void localTransactionRolledback() {
           ConnectionEvent event = new ConnectionEvent(this, 
ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK);
  -        Object [] elements = listeners.toArray();
  -        for(int i = 0; i < elements.length; i++){
  -            ConnectionEventListener eventListener = 
(ConnectionEventListener)elements[i];
  +        Object[] elements = listeners.toArray();
  +        for (int i = 0; i < elements.length; i++) {
  +            ConnectionEventListener eventListener = 
(ConnectionEventListener) elements[i];
               eventListener.localTransactionRolledback(event);
           }
       }
   
  -    protected void localTransactionStarted(){
  +    protected void localTransactionStarted() {
           ConnectionEvent event = new ConnectionEvent(this, 
ConnectionEvent.LOCAL_TRANSACTION_STARTED);
  -        Object [] elements = listeners.toArray();
  -        for(int i = 0; i < elements.length; i++){
  -            ConnectionEventListener eventListener = 
(ConnectionEventListener)elements[i];
  +        Object[] elements = listeners.toArray();
  +        for (int i = 0; i < elements.length; i++) {
  +            ConnectionEventListener eventListener = 
(ConnectionEventListener) elements[i];
               eventListener.localTransactionStarted(event);
           }
       }
   
  -    protected void connectionErrorOccurred(JdbcConnection jdbcConn, 
java.sql.SQLException sqlE){
  +    protected void connectionErrorOccurred(JdbcConnection jdbcConn, 
java.sql.SQLException sqlE) {
   
  -        if(logWriter !=null){
  +        if (logWriter != null) {
               logWriter.print("\nJdbcConnection Error: On java.sql.Connection 
(");
               logWriter.print(jdbcConn);
               logWriter.println(")");
               logWriter.println("Exception Stack trace follows:");
               sqlE.printStackTrace(logWriter);
               java.sql.SQLException temp = sqlE;
  -            while((temp = sqlE.getNextException())!= null){
  +            while ((temp = sqlE.getNextException()) != null) {
                   temp.printStackTrace(logWriter);
               }
           }
   
  -        ConnectionEvent event = new ConnectionEvent(this, 
ConnectionEvent.CONNECTION_ERROR_OCCURRED , sqlE);
  -        Object [] elements = listeners.toArray();
  -        for(int i = 0; i < elements.length; i++){
  -            ConnectionEventListener eventListener = 
(ConnectionEventListener)elements[i];
  +        ConnectionEvent event = new ConnectionEvent(this, 
ConnectionEvent.CONNECTION_ERROR_OCCURRED, sqlE);
  +        Object[] elements = listeners.toArray();
  +        for (int i = 0; i < elements.length; i++) {
  +            ConnectionEventListener eventListener = 
(ConnectionEventListener) elements[i];
               eventListener.connectionErrorOccurred(event);
           }
       }
   
       /**
  -    * Invoked by the JdbcConneciton when its close() method is called.
  -    * This method invalidates the JdbcConnection handle, removes it from
  -    * the list of active handles and notifies all the 
ConnectionEventListeners.
  -    */
  -    protected void connectionClose(JdbcConnection jdbcConn){
  -         synchronized(jdbcConnections)
  -         {
  -             jdbcConn.invalidate();
  -             jdbcConnections.remove(jdbcConn);
  -             ConnectionEvent event = new ConnectionEvent(this, 
ConnectionEvent.CONNECTION_CLOSED);
  -             Object [] elements = listeners.toArray();
  -             for(int i = 0; i < elements.length; i++){
  -                 ConnectionEventListener eventListener = 
(ConnectionEventListener)elements[i];
  -                 eventListener.connectionClosed(event);
  -             }
  -             }
  +     * Invoked by the JdbcConneciton when its close() method is called.
  +     * This method invalidates the JdbcConnection handle, removes it from
  +     * the list of active handles and notifies all the 
ConnectionEventListeners.
  +     */
  +    protected void connectionClose(JdbcConnection jdbcConn) {
  +        synchronized (jdbcConnections) {
  +            jdbcConn.invalidate();
  +            jdbcConnections.remove(jdbcConn);
  +            ConnectionEvent event = new ConnectionEvent(this, 
ConnectionEvent.CONNECTION_CLOSED);
  +            Object[] elements = listeners.toArray();
  +            for (int i = 0; i < elements.length; i++) {
  +                ConnectionEventListener eventListener = 
(ConnectionEventListener) elements[i];
  +                eventListener.connectionClosed(event);
  +            }
  +        }
       }
   
  -    public String toString( ){
  -        return "JdbcManagedConnection ("+sqlConn.toString()+")";
  +    public String toString() {
  +        return "JdbcManagedConnection (" + sqlConn.toString() + ")";
       }
   }
  
  
  

Reply via email to