/*

   Derby - Class org.apache.derby.jdbc.BasicClientDataSource40

   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.derby.jdbc;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedAction;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import javax.sql.DataSource;
import javax.sql.PooledConnection;
import javax.sql.XAConnection;
import org.apache.derby.client.am.ClientMessageId;
import org.apache.derby.client.am.EncryptionManager;
import org.apache.derby.client.am.LogWriter;
import org.apache.derby.client.am.SQLExceptionFactory;
import org.apache.derby.client.am.SqlException;
import org.apache.derby.client.net.NetConfiguration;
import org.apache.derby.client.net.NetLogWriter;
import org.apache.derby.shared.common.error.ExceptionUtil;
import org.apache.derby.shared.common.reference.Attribute;
import org.apache.derby.shared.common.reference.SQLState;

/**
 * This data source is suitable for client/server use of Derby,
 * running on Java 8 Compact Profile 2 or higher.
 * <p/>
 * BasicClientDataSource40 is similar to ClientDataSource except it
 * can not be used with JNDI, i.e. it does not implement
 * {@code javax.naming.Referenceable}.
 * <p/>
 *  * The standard attributes provided are, cf. e.g. table
 * 9.1 in the JDBC 4.2 specification.
 * <ul>
 *   <li>databaseName</li>
 *   <li>dataSourceName</li>
 *   <li>description</li>
 *   <li>password</li>
 *   <li>user</li>
 * </ul>
 * These standard attributes are not supported:
 * <ul>
 *   <li>networkProtocol</li>
 *   <li>roleName</li>
 * </ul>
 * The Derby client driver also supports these attributes:
 * <ul>
 *   <li>loginTimeout</li> @see javax.sql.CommonDataSource set/get
 *   <li>logWriter</li> @see javax.sql.CommonDataSource set/get
 *   <li>createDatabase</li>
 *   <li>connectionAttributes</li>
 *   <li>shutdownDatabase</li>
 *   <li>attributesAsPassword</li>
 *   <li>retrieveMessageText</li>
 *   <li>securityMechanism</li>
 *   <li>traceDirectory</li>
 *   <li>traceFile</li>
 *   <li>traceFileAppend</li>
 *   <li>traceLevel<li>
 * </ul>
 */
@SuppressWarnings("ResultOfObjectAllocationIgnored")
public class BasicClientDataSource40 
    implements DataSource, ClientDataSourceInterface, Serializable {

    private final static long serialVersionUID = 1894299584216955554L;
    public final static String className__ =
            "org.apache.derby.jdbc.BasicClientDataSource40";

    // ---------------------------- traceLevel -------------------------------
    //

    /**
     * The client server protocol can be traced. The constants below define the
     * tracing level, cf. the documentation section "Network Client Tracing" in
     * the "Derby Server and Administration Guide". Cf. the connection
     * attribute (or data source bean property) {@code traceLevel}.
     *
     * <pre>
     * TRACE_NONE	
     * TRACE_CONNECTION_CALLS	
     * TRACE_STATEMENT_CALLS	
     * TRACE_RESULT_SET_CALLS	
     * TRACE _DRIVER_CONFIGURATION	
     * TRACE_CONNECTS	
     * TRACE_PROTOCOL_FLOWS	
     * TRACE _RESULT_SET_META_DATA	
     * TRACE _PARAMETER_META_DATA	
     * TRACE_DIAGNOSTICS	
     * TRACE_XA_CALLS	
     * TRACE_ALL	
     * </pre>
     */
    public final static int TRACE_NONE = 0x0;
    /**
     * See documentation at {@link #TRACE_NONE}.
     */
    public final static int TRACE_CONNECTION_CALLS = 0x1;
    /**
     * See documentation at {@link #TRACE_NONE}.
     */
    public final static int TRACE_STATEMENT_CALLS = 0x2;
    /**
     * See documentation at {@link #TRACE_NONE}.
     */
    public final static int TRACE_RESULT_SET_CALLS = 0x4;
    /**
     * See documentation at {@link #TRACE_NONE}.
     */
    /**
     * See documentation at {@link #TRACE_NONE}.
     */
    public final static int TRACE_DRIVER_CONFIGURATION = 0x10;
    /**
     * See documentation at {@link #TRACE_NONE}.
     */
    public final static int TRACE_CONNECTS = 0x20;
    /**
     * See documentation at {@link #TRACE_NONE}.
     */
    public final static int TRACE_PROTOCOL_FLOWS = 0x40;
    /**
     * See documentation at {@link #TRACE_NONE}.
     */
    public final static int TRACE_RESULT_SET_META_DATA = 0x80;
    /**
     * See documentation at {@link #TRACE_NONE}.
     */
    public final static int TRACE_PARAMETER_META_DATA = 0x100;
    /**
     * See documentation at {@link #TRACE_NONE}.
     */
    public final static int TRACE_DIAGNOSTICS = 0x200;
    /**
     * See documentation at {@link #TRACE_NONE}.
     */
    public final static int TRACE_XA_CALLS = 0x800;
    /**
     * See documentation at {@link #TRACE_NONE}.
     */
    public final static int TRACE_ALL = 0xFFFFFFFF;
    /**
     * See documentation at {@link #TRACE_NONE}.
     */
    public final static int propertyDefault_traceLevel = TRACE_ALL;

    static
    {
        try {
            // The EncryptionManager class will instantiate objects of the
            // required security algorithms that are needed for EUSRIDPWD An
            // exception will be thrown if support is not available in the JCE
            // implementation in the JVM in which the client is loaded.
            new EncryptionManager(null);
        } catch(Exception e) {
            // if an exception is thrown, ignore exception.
        }

    }

    /**
     * Creates a simple DERBY data source with default property values
     * for a non-pooling, non-distributed environment.  No particular
     * DatabaseName or other properties are associated with the data
     * source.
     * <p/>
     * Every Java Bean should provide a constructor with no arguments
     * since many beanboxes attempt to instantiate a bean by invoking
     * its no-argument constructor.
     */
    public BasicClientDataSource40() {
        super();
    }

    /**
     * The source security mechanism to use when connecting to a client data
     * source.
     * <p/>
     * Security mechanism options are:
     * <ul>
     *   <li> USER_ONLY_SECURITY
     *   <li> CLEAR_TEXT_PASSWORD_SECURITY
     *   <li> ENCRYPTED_PASSWORD_SECURITY
     *   <li> ENCRYPTED_USER_AND_PASSWORD_SECURITY - both password and
     *        user are encrypted
     *   <li> STRONG_PASSWORD_SUBSTITUTE_SECURITY
     * </ul> The default security mechanism is USER_ONLY SECURITY
     * <p/>
     * If the application specifies a security mechanism then it will be the
     * only one attempted. If the specified security mechanism is not
     * supported by the conversation then an exception will be thrown and
     * there will be no additional retries.
     * <p/>
     * Both user and password need to be set for all security mechanism except
     * USER_ONLY_SECURITY.
     */
    public final static short USER_ONLY_SECURITY =
        ClientDataSourceInterface.USER_ONLY_SECURITY;

    /**
     * See documentation at {@link #USER_ONLY_SECURITY}
     */
    public final static short CLEAR_TEXT_PASSWORD_SECURITY =
        ClientDataSourceInterface.CLEAR_TEXT_PASSWORD_SECURITY;

    /**
     * See documentation at {@link #USER_ONLY_SECURITY}
     */
    public final static short ENCRYPTED_PASSWORD_SECURITY =
        ClientDataSourceInterface.ENCRYPTED_PASSWORD_SECURITY;

    /**
     * See documentation at {@link #USER_ONLY_SECURITY}
     */
    public final static short ENCRYPTED_USER_AND_PASSWORD_SECURITY =
        ClientDataSourceInterface.ENCRYPTED_USER_AND_PASSWORD_SECURITY;

    /**
     * See documentation at {@link #USER_ONLY_SECURITY}
     */
    public final static short STRONG_PASSWORD_SUBSTITUTE_SECURITY =
        ClientDataSourceInterface.STRONG_PASSWORD_SUBSTITUTE_SECURITY;

    // The loginTimeout jdbc 2 data source property is not supported as a jdbc
    // 1 connection property, because loginTimeout is set by the jdbc 1 api
    // via java.sql.DriverManager.setLoginTimeout().  The databaseName,
    // serverName, and portNumber data source properties are also not
    // supported as connection properties because they are extracted from the
    // jdbc 1 database url passed on the connection request.  However, all
    // other data source properties should probably also be supported as
    // connection properties.

    // ---------------------------- loginTimeout ------------------------------
    //
    // was serialized in 1.0 release
    /**
     * The time in seconds to wait for a connection request on this data
     * source. The default value of zero indicates that either the system time
     * out be used or no timeout limit.
     *
     * @serial
     */
    private int loginTimeout;

    public synchronized void setLoginTimeout(int seconds) {
        this.loginTimeout = seconds;
    }

    public int getLoginTimeout() {
        return this.loginTimeout;
    }

    // ---------------------------- logWriter --------------------------------
    //
    /**
     * The log writer is declared transient, and is not serialized or stored
     * under JNDI.
     *
     * @see #traceLevel
     */
    private transient PrintWriter logWriter;

    public synchronized void setLogWriter(PrintWriter logWriter) {
        this.logWriter = logWriter;
    }

    public PrintWriter getLogWriter() {
        return this.logWriter;
    }

    // ---------------------------- databaseName ------------------------------
    //
    
    /**
     * Stores the relational database name, RDBNAME.
     * The length of the database name may be limited to 18 bytes
     * and therefore may throw an SQLException.
     * @serial
     */
    private String databaseName;

    // databaseName is not permitted in a properties object


    // ---------------------------- description ------------------------------
    
    /**
     * A description of this data source.
     * @serial
     */
    private String description;

    // ---------------------------- dataSourceName ----------------------------
    //
    
    /**
     * A data source name;
     * used to name an underlying XADataSource,
     * or ConnectionPoolDataSource when pooling of connections is done.
     * @serial
     */
    private String dataSourceName;

    // ---------------------------- portNumber --------------------------------

    /**
     * @serial
     */
    private int portNumber = propertyDefault_portNumber;

    // ---------------------------- serverName --------------------------------
    
    /**
     * @serial 
     */
    private String serverName = propertyDefault_serverName; // Derby-410 fix.

    // serverName is not permitted in a properties object

    //---------------------- client SSL ----------------

    /** The constant indicating that SSL encryption won't be used. */
    public final static int SSL_OFF = 0;
    private final static String SSL_OFF_STR = "off";
    /** The constant indicating that SSL encryption will be used. */
    public final static int SSL_BASIC = 1;
    private final static String SSL_BASIC_STR = "basic";
    /**
     * The constant indicating that SSL encryption with peer authentication
     * will be used.
     */
    public final static int SSL_PEER_AUTHENTICATION = 2;
    private final static String SSL_PEER_AUTHENTICATION_STR =
            "peerAuthentication";

    /**
     * Parses the string and returns the corresponding constant for the SSL
     * mode denoted.
     * <p>
     * Valid values are <tt>off</tt>, <tt>basic</tt> and
     * <tt>peerAuthentication</tt>.
     *
     * @param s string denoting the SSL mode
     * @return A constant indicating the SSL mode denoted by the string. If the
     *      string is {@code null}, {@link #SSL_OFF} is returned.
     * @throws SqlException if the string has an invalid value
     */
    public static int getSSLModeFromString(String s)
        throws SqlException
    {

        if (s != null){
            if (s.equalsIgnoreCase(SSL_OFF_STR)) {
                return SSL_OFF;
            } else if (s.equalsIgnoreCase(SSL_BASIC_STR)) {
                return SSL_BASIC;
            } else if (s.equalsIgnoreCase(SSL_PEER_AUTHENTICATION_STR)) {
                return SSL_PEER_AUTHENTICATION;
            } else {
                throw new SqlException(null,
                        new ClientMessageId(SQLState.INVALID_ATTRIBUTE),
                        Attribute.SSL_ATTR, s, SSL_OFF_STR + ", " +
                        SSL_BASIC_STR + ", " + SSL_PEER_AUTHENTICATION_STR);
            }
        } else {
            // Default
            return SSL_OFF;
        }
    }

    /**
     * Returns the SSL mode specified by the property object.
     *
     * @param properties data source properties
     * @return A constant indicating the SSL mode to use. Defaults to
     *      {@link #SSL_OFF} if the SSL attribute isn't specified.
     * @throws SqlException if an invalid value for the SSL mode is specified
     *      in the property object
     */
    public static int getClientSSLMode(Properties properties)
        throws SqlException
    {
        return
            getSSLModeFromString(properties.getProperty(Attribute.SSL_ATTR));
    }

    // ---------------------------- user -----------------------------------
    //
    
    /**
     * This property can be overwritten by specifing the
     * username parameter on the DataSource.getConnection() method
     * call.  If user is specified, then password must also be
     * specified, either in the data source object or provided on
     * the DataSource.getConnection() call.
     *
     * Each data source implementation subclass will maintain it's own
     * <code>password</code> property.  This password property may or may not
     * be declared transient, and therefore may be serialized to a file in
     * clear-text, care must taken by the user to prevent security breaches.
     * Derby-406 fix
     * 
     * @serial
     */
    private String user = propertyDefault_user;

    public static String getUser(Properties properties) {
        String userString = properties.getProperty(Attribute.USERNAME_ATTR);
        return parseString(userString, propertyDefault_user);
    }

    // ---------------------------- securityMechanism -------------------------
    //
    // The source security mechanism to use when connecting to this data
    // source.
    // <p>
    // Security mechanism options are:
    // <ul>
    // <li> USER_ONLY_SECURITY
    // <li> CLEAR_TEXT_PASSWORD_SECURITY
    // <li> ENCRYPTED_PASSWORD_SECURITY
    // <li> ENCRYPTED_USER_AND_PASSWORD_SECURITY - both password and
    //      user are encrypted
    // <li> STRONG_PASSWORD_SUBSTITUTE_SECURITY
    // </ul>
    // The default security mechanism is USER_ONLY_SECURITY.
    // <p>
    // If the application specifies a security
    // mechanism then it will be the only one attempted.
    // If the specified security mechanism is not supported by the
    // conversation then an exception will be thrown and there will be no
    // additional retries.
    // <p>
    // This property is currently only available for the DNC driver.
    // <p>
    // Both user and password need to be set for all security mechanism except
    // USER_ONLY_SECURITY When using USER_ONLY_SECURITY, only the user
    // property needs to be specified.
    //

    // constant to indicate that the security mechanism has not been
    // explicitly set, either on connection request when using DriverManager
    // or on the Client DataSource object
    private final static short SECMEC_HAS_NOT_EXPLICITLY_SET = 0;

    /**
     * Security Mechanism can be specified explicitly either when obtaining a
     * connection via a DriverManager or via Datasource.
     * Via DriverManager, securityMechanism can be set on the connection
     * request using the 'securityMechanism' attribute.
     * Via DataSource, securityMechanism can be set by calling
     * setSecurityMechanism() on the ClientDataSource
     * If the security mechanism is not explicitly set as mentioned above, in
     * that case the Client will try to upgrade the security mechanism to a
     * more secure one, if possible.
     * See {@link #getUpgradedSecurityMechanism}.
     * Therefore, need to keep track if the securityMechanism has been
     * explicitly set.
     *
     * @serial
     */
    private short securityMechanism = SECMEC_HAS_NOT_EXPLICITLY_SET;



    // We use the NET layer constants to avoid a mapping for the NET driver.
    
    /**
     * Return security mechanism if it is set, else upgrade the security
     * mechanism if possible and return the upgraded security mechanism
     * @param properties Look in the properties if securityMechanism is set
     *        or not
     * if set, return this security mechanism
     * @return security mechanism
     */
    public static short getSecurityMechanism(Properties properties) {
        short secmec;
        String securityMechanismString =
            properties.getProperty(Attribute.CLIENT_SECURITY_MECHANISM);

        if ( securityMechanismString != null )
        {
            // security mechanism has been set, do not override, but instead
            // return the security mechanism that has been set (DERBY-962)
            secmec = Short.parseShort(securityMechanismString);
        }
        else
        {
            // if securityMechanismString is null, this means that security
            // mechanism has not been set explicitly and not available in
            // properties. Hence, do an upgrade of security mechanism if
            // possible The logic for upgrade of security mechanism uses
            // information about if password is available or not, so pass this
            // information also.
            String passwordString =
                properties.getProperty(Attribute.PASSWORD_ATTR);
            secmec = getUpgradedSecurityMechanism(passwordString);
        }
        return secmec;
    }

    /**
     * This method has logic to upgrade security mechanism to a better (more
     * secure) one if it is possible. Currently derby server only has support
     * for USRIDPWD, USRIDONL, EUSRIDPWD and USRSSBPWD (10.2+) - this method
     * only considers these possibilities. USRIDPWD, EUSRIDPWD and USRSSBPWD
     * require a password, USRIDONL is the only security mechanism which does
     * not require password.
     * 1. if password is not available, then security mechanism possible is
     * USRIDONL
     * 2. if password is available,then USRIDPWD is returned.
     *
     * @param password password argument
     * @return upgraded security mechanism if possible
     */
    private static short getUpgradedSecurityMechanism(String password) {
        // if password is null, in that case the only acceptable security
        // mechanism is USRIDONL, which is the default security mechanism.
        if ( password == null ) {
            return propertyDefault_securityMechanism;
        }

        // when we have support for more security mechanisms on server
        // and client, we should update this upgrade logic to pick
        // secure security mechanisms before trying out the USRIDPWD

        /*
        // -----------------------
        // PLEASE NOTE:
        // When DERBY-1517, DERBY-1755 is fixed, there might be a way to use
        // EUSRIDPWD when both client and server vm's have support for
        // it. Hence the below if statement is commented out.
      if (SUPPORTS_EUSRIDPWD)
            return (short)NetConfiguration.SECMEC_EUSRIDPWD;
        else
            // IMPORTANT NOTE:
            // --------------
            // If DERBY-1517 can be fixed, we should default to
            // SECMEC_USRSSBPWD (strong password substitute).
            // Until then, connecting with a 10.2+ client to
            // a derby server < 10.2, and hence does not support
            // SECMEC_USRSSBPWD as a SECMEC, will cause a DRDA protocol
            // exception, as described in DERBY-926).
            //
            // return (short)NetConfiguration.SECMEC_USRSSBPWD;
         // ----------------------
         */
         return (short)NetConfiguration.SECMEC_USRIDPWD;

    }

    // ---------------------------- getServerMessageTextOnGetMessage ---------

    /**
     * @serial
     */
    private boolean retrieveMessageText = propertyDefault_retrieveMessageText;

    public static boolean getRetrieveMessageText(Properties properties) {
        String retrieveMessageTextString =
            properties.getProperty(Attribute.CLIENT_RETIEVE_MESSAGE_TEXT);
        return parseBoolean(
            retrieveMessageTextString, propertyDefault_retrieveMessageText);
    }

    // ---------------------------- traceFile ---------------------------------

    /**
     * @serial
     */
    private String traceFile;

    static String getTraceFile(Properties properties) {
        return properties.getProperty(Attribute.CLIENT_TRACE_FILE);
    }

    // ---------------------------- traceDirectory ----------------------------
    // For the suffix of the trace file when traceDirectory is enabled.
    private transient int traceFileSuffixIndex_ = 0;

    /**
     * @serial
     */
    private String traceDirectory;

    /**
     * Check if derby.client.traceDirectory is provided as a JVM property.
     * If yes, then we use that value. If not, then we look for traceDirectory
     * in the the properties parameter.
     *
     * @param properties jdbc url properties
     * @return value of traceDirectory property
     */
    static String getTraceDirectory(Properties properties) {
        String traceDirectoryString;

        traceDirectoryString  =
            readSystemProperty(
                Attribute.CLIENT_JVM_PROPERTY_PREFIX +
                Attribute.CLIENT_TRACE_DIRECTORY);

        if (traceDirectoryString == null  && properties != null) {
            return properties.getProperty(Attribute.CLIENT_TRACE_DIRECTORY);
        } else {
            return traceDirectoryString;
        }
    }


    /**
     * Read the value of the passed system property. If we are running under
     * the Java security manager and permission to read the property is 
     * missing,a null is returned, and no diagnostic is given (DERBY-6620).
     * 
     * @param key name of the system property
     * @return value of the system property, null if there is no
     *         permission to read the property
     */
    private static String readSystemProperty(final String key) {
        return AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    try {
                        return System.getProperty(key);
                    } catch (SecurityException se) {
                        // We do not want the connection to fail if the user
                        // does not have permission to read the property, so
                        // if a security exception occurs, just return null
                        // and continue with the connection.
                        // See also the discussion in DERBY-6620 on why we do
                        // not write a warning message on the console here.
                        return null;
                    }
                }
            }
            );
    }

    // ---------------------------- traceFileAppend ---------------------------

    /**
     * @serial
     */
    private boolean traceFileAppend = propertyDefault_traceFileAppend;

    static boolean getTraceFileAppend(Properties properties) {
        String traceFileAppendString =
            properties.getProperty(Attribute.CLIENT_TRACE_APPEND);
        return parseBoolean(
            traceFileAppendString, propertyDefault_traceFileAppend);
    }

    // ---------------------------- password ----------------------------------
    //
    // The password property is defined in subclasses, but the method
    // getPassword (java.util.Properties properties) is in this class to
    // eliminate dependencies on j2ee for connections that go thru the driver
    // manager.

    public static String getPassword(Properties properties) {
        return properties.getProperty("password");
    }

    /**
     * @serial
     */
    private String password;

    synchronized public void setPassword(String password) {
        this.password = password;
    }

    public String getPassword() {
        return password;
    }


    // ----------------------supplemental methods------------------------------


    //---------------------- helper methods -----------------------------------

    // The java.io.PrintWriter overrides the traceFile setting.
    // If neither traceFile nor jdbc logWriter are set, then null is returned.
    // logWriterInUseSuffix used only for trace directories to indicate whether
    // log writer is use is from xads, cpds, sds, ds, driver, config, reset.
    private LogWriter computeDncLogWriterForNewConnection(
        String logWriterInUseSuffix) throws SqlException {

        return computeDncLogWriterForNewConnection(
            logWriter,
            traceDirectory,
            traceFile,
            traceFileAppend,
            traceLevel,
            logWriterInUseSuffix,
            traceFileSuffixIndex_++);
    }

    // Called on for connection requests.  The java.io.PrintWriter overrides
    // the traceFile setting.  If neither traceFile, nor logWriter, nor
    // traceDirectory are set, then null is returned.
    static LogWriter computeDncLogWriterForNewConnection(
        PrintWriter logWriter,
        String traceDirectory,
        String traceFile,
        boolean traceFileAppend,
        int traceLevel,
        String logWriterInUseSuffix,
        int traceFileSuffixIndex) throws SqlException {

        // compute regular dnc log writer if there is any
        LogWriter dncLogWriter = computeDncLogWriter(
            logWriter,
            traceDirectory,
            traceFile,
            traceFileAppend,
            logWriterInUseSuffix,
            traceFileSuffixIndex,
            traceLevel);

        return dncLogWriter;
    }

    // Compute a DNC log writer before a connection is created.
    private static LogWriter computeDncLogWriter(
        PrintWriter logWriter,
        String traceDirectory,
        String traceFile,
        boolean traceFileAppend,
        String logWriterInUseSuffix,
        int traceFileSuffixIndex,
        int traceLevel) throws SqlException {

        // Otherwise, the trace file will still be created even TRACE_NONE.
        if (traceLevel == TRACE_NONE) {
            return null;
        }

        PrintWriter printWriter = computePrintWriter(
            logWriter,
            traceDirectory,
            traceFile,
            traceFileAppend,
            logWriterInUseSuffix,
            traceFileSuffixIndex);

        if (printWriter == null) {
            return null;
        }

        LogWriter dncLogWriter = new NetLogWriter(printWriter, traceLevel);
        if (printWriter != logWriter &&
                (traceDirectory != null || traceFile != null))
        // When printWriter is an internal trace file and
        // traceDirectory is not null, each connection has
        // its own trace file and the trace file is not cached,
        // so we can close it when DNC log writer is closed.
        {
            dncLogWriter.printWriterNeedsToBeClosed_ = true;
        }
        return dncLogWriter;
    }

    // This method handles all the override semantics.  The logWriter
    // overrides the traceFile, and traceDirectory settings.  If neither
    // traceFile, nor logWriter, nor traceDirectory are set, then null is
    // returned.
    private static PrintWriter computePrintWriter(
        PrintWriter logWriter,
        String traceDirectory,
        String traceFile,
        boolean traceFileAppend,
        String logWriterInUseSuffix,
        int traceFileSuffixIndex) throws SqlException {

        if (logWriter != null)  // java.io.PrintWriter is specified
        {
            return logWriter;
        } else { // check trace file setting.
            if (traceDirectory != null) {
                String fileName;
                if (traceFile == null) {
                    fileName = traceDirectory + File.separator +
                        logWriterInUseSuffix + "_" + traceFileSuffixIndex;
                } else {
                    fileName = traceDirectory + File.separator +
                        traceFile + logWriterInUseSuffix + "_" +
                        traceFileSuffixIndex;
                }
                return getPrintWriter(
                    fileName, true); // no file append and not enable caching.
            } else if (traceFile != null) {
                return getPrintWriter(traceFile, traceFileAppend);
            }
        }
        return null;
    }

    private static PrintWriter getPrintWriter(
            final String fileName,
            final boolean fileAppend) throws SqlException {

        PrintWriter printWriter = null;
        //Using an anonymous class to deal with the PrintWriter because the  
        //method java.security.AccessController.doPrivileged requires an 
        //instance of a class(which implements 
        //java.security.PrivilegedExceptionAction). Since getPrintWriter method
        //is static, we can't simply pass "this" to doPrivileged method and 
        //have LogWriter implement PrivilegedExceptionAction.
        //To get around the static nature of method getPrintWriter, have an
        //anonymous class implement PrivilegedExceptionAction. That class will 
        //do the work related to PrintWriter in it's run method and return 
        //PrintWriter object.
        try {
            printWriter = AccessController.doPrivileged(
                new PrivilegedExceptionAction<PrintWriter>(){
                    public PrintWriter run() throws IOException {
                        String fileCanonicalPath =
                            new File(fileName).getCanonicalPath();
                        return new PrintWriter(
                                new BufferedOutputStream(
                                        new FileOutputStream(
                                                fileCanonicalPath, fileAppend), 4096), true);
                        }
                    });
        } catch (PrivilegedActionException pae) {
            throw new SqlException(null, 
                new ClientMessageId(SQLState.UNABLE_TO_OPEN_FILE),
                new Object[] { fileName, pae.getMessage() },
                pae);
        }
        return printWriter;
    }
    
    private static boolean parseBoolean(
        String boolString, boolean defaultBool) {

        if (boolString != null) {
            return (boolString.equalsIgnoreCase("true") ||
                    boolString.equalsIgnoreCase("yes"));
        }
        return defaultBool;
    }

    private static String parseString(String string, String defaultString) {
        if (string != null) {
            return string;
        }
        return defaultString;
    }

    private static int parseInt(String intString, int defaultInt) {
        if (intString != null) {
            return Integer.parseInt(intString);
        }
        return defaultInt;
    }

    // tokenize "property=value;property=value..." and returns new properties
    //object This method is used both by ClientDriver to parse the url and
    //ClientDataSource.setConnectionAttributes
    static Properties tokenizeAttributes(
        String attributeString, Properties properties) throws SqlException {

        Properties augmentedProperties;

        if (attributeString == null) {
            return properties;
        }

        if (properties != null) {
            augmentedProperties = (Properties) properties.clone();
        } else {
            augmentedProperties = new Properties();
        }
        try {
            StringTokenizer attrTokenizer =
                new StringTokenizer(attributeString, ";");

            while (attrTokenizer.hasMoreTokens()) {
                String v = attrTokenizer.nextToken();

                int eqPos = v.indexOf('=');
                if (eqPos == -1) {
                    throw new SqlException(null,
                        new ClientMessageId(SQLState.INVALID_ATTRIBUTE_SYNTAX),
                        attributeString);
                }

                augmentedProperties.setProperty(
                    (v.substring(0, eqPos)).trim(),
                    (v.substring(eqPos + 1)).trim());
            }
        } catch (NoSuchElementException e) {
            // A null log writer is passed, because jdbc 1 sqlexceptions are
            // automatically traced
            throw new SqlException(null,
                new ClientMessageId(SQLState.INVALID_ATTRIBUTE_SYNTAX),
                e, attributeString);
        }
        checkBoolean(augmentedProperties,
                     Attribute.CLIENT_RETIEVE_MESSAGE_TEXT);
        return augmentedProperties;

    }

    private static void checkBoolean(Properties set, String attribute)
            throws SqlException {

        final String[] booleanChoices = {"true", "false"};
        checkEnumeration(set, attribute, booleanChoices);
    }


    private static void checkEnumeration(
        Properties set,
        String attribute,
        String[] choices) throws SqlException {

        String value = set.getProperty(attribute);
        if (value == null) {
            return;
        }

        for (int i = 0; i < choices.length; i++) {
            if (value.toUpperCase(Locale.ENGLISH).equals(
                        choices[i].toUpperCase(Locale.ENGLISH))) {
                return;
            }
        }

// The attribute value is invalid. Construct a string giving the choices for
// display in the error message.
        String choicesStr = "{";
        for (int i = 0; i < choices.length; i++) {
            if (i > 0) {
                choicesStr += "|";
            }
            choicesStr += choices[i];
        }

        throw new SqlException(null,
            new ClientMessageId(SQLState.INVALID_ATTRIBUTE),
            attribute, value, choicesStr);
    }

    /*
     * Properties to be seen by Bean - access thru reflection.
     */

    // -- Standard JDBC DataSource Properties

    public synchronized void setDatabaseName(String databaseName) {
        this.databaseName = databaseName;
    }

    public String getDatabaseName() {
        return this.databaseName;
    }


    public synchronized void setDataSourceName(String dataSourceName) {
        this.dataSourceName = dataSourceName;
    }

    public String getDataSourceName() {
        return this.dataSourceName;
    }

    public synchronized void setDescription(String description) {
        this.description = description;
    }

    public String getDescription() {
        return this.description;
    }


    public synchronized void setPortNumber(int portNumber) {
        this.portNumber = portNumber;
    }

    public int getPortNumber() {
        return this.portNumber;
    }

    public synchronized void setServerName(String serverName) {
        this.serverName = serverName;
    }

    public String getServerName() {
        return this.serverName;
    }


    public synchronized void setUser(String user) {
        this.user = user;
    }

    public String getUser() {
        return this.user;
    }

    synchronized public void setRetrieveMessageText(
        boolean retrieveMessageText) {

        this.retrieveMessageText = retrieveMessageText;
    }

    public boolean getRetrieveMessageText() {
        return this.retrieveMessageText;
    }


    /**
     * Sets the security mechanism.
     * @param securityMechanism to set
     */
    synchronized public void setSecurityMechanism(short securityMechanism) {
        this.securityMechanism = securityMechanism;
    }

    /**
     * Return the security mechanism.
     * If security mechanism has not been set explicitly on datasource,
     * then upgrade the security mechanism to a more secure one if possible.
     * @see #getUpgradedSecurityMechanism(String)
     * @return the security mechanism
     */
    public short getSecurityMechanism() {
        return getSecurityMechanism(getPassword());
    }

    /**
     * Return the security mechanism for this datasource object.
     * If security mechanism has not been set explicitly on datasource,
     * then upgrade the security mechanism to a more secure one if possible.
     * @param password  password of user
     * @see #getUpgradedSecurityMechanism(String)
     * @return the security mechanism
     */
    public short getSecurityMechanism(String password) {

        // if security mechanism has not been set explicitly on
        // datasource, then upgrade the security mechanism if possible
        // DERBY-962
        if ( securityMechanism == SECMEC_HAS_NOT_EXPLICITLY_SET ) {
            return getUpgradedSecurityMechanism(password);
        }

        return securityMechanism;
    }

    // ----------------------- ssl

    /**
     * @serial
     */
    private int sslMode;

    /**
     * Specifies the SSL encryption mode to use.
     * <p>
     * Valid values are <tt>off</tt>, <tt>basic</tt> and
     * <tt>peerAuthentication</tt>.
     *
     * @param mode the SSL mode to use (<tt>off</tt>, <tt>basic</tt> or
     *      <tt>peerAuthentication</tt>)
     * @throws SqlException if the specified mode is invalid
     */
    public void setSsl(String mode)
        throws SqlException
    {
        sslMode = getSSLModeFromString(mode);
    }

    /**
     * Returns the SSL encryption mode specified for the data source.
     *
     * @return <tt>off</tt>, <tt>basic</tt> or <tt>peerAuthentication</tt>.
     */
    public String getSsl() {
        switch(sslMode) {
        case SSL_OFF:
        default:
            return SSL_OFF_STR;
        case SSL_BASIC:
            return SSL_BASIC_STR;
        case SSL_PEER_AUTHENTICATION:
            return SSL_PEER_AUTHENTICATION_STR;
        }
    }

    // ----------------------- set/getCreate/ShutdownDatabase -----------------
    /**
     * Set to true if the database should be created.
     * @serial
     */
    private boolean createDatabase;

    /**
     * Set to true if the database should be shutdown.
     * @serial
     */
    private boolean shutdownDatabase;

    /**
     * Set this property to create a new database.  If this property is not
     * set, the database (identified by databaseName) is assumed to be already
     * existing.
     * @param create if set to the string "create", this data source will try
     *               to create a new database of databaseName, or boot the
     *               database if one by that name already exists.
     *
     */
    public void setCreateDatabase(String create) {
        if (create != null && create.equalsIgnoreCase("create")) {
            this.createDatabase = true;
        } else { // reset
            this.createDatabase = false;
        }
    }

    /** @return "create" if create is set, or null if not
     */
    public String getCreateDatabase() {
        String createstr=null;
        if (createDatabase) {
            createstr="create";
        }
        return createstr;
    }

    /**
     * Set this property if one wishes to shutdown the database identified by
     * databaseName.
     * @param shutdown if set to the string "shutdown", this data source will
     *                 shutdown the database if it is running.
     *
     */
    public void setShutdownDatabase(String shutdown) {
        if (shutdown != null && shutdown.equalsIgnoreCase("shutdown")) {
            this.shutdownDatabase = true;
        } else { // reset
            this.shutdownDatabase = false;
        }
    }

    /** @return "shutdown" if shutdown is set, or null if not
     */
    public String getShutdownDatabase() {
        String shutdownstr=null;
        if (shutdownDatabase)
        {
            shutdownstr = "shutdown";
        }
        return shutdownstr;
    }

    /**
     * @serial
     */
    private String connectionAttributes = null;

    /**
     * Set this property to pass in more Derby specific connection URL
     * attributes.
     * <BR>
     * Any attributes that can be set using a property of this DataSource
     * implementation (e.g user, password) should not be set in
     * connectionAttributes. Conflicting settings in connectionAttributes and
     * properties of the DataSource will lead to unexpected behaviour.
     *
     * @param prop set to the list of Derby connection attributes separated by
     *    semi-colons.  E.g., to specify an encryption bootPassword
     *    of "x8hhk2adf", and set upgrade to true, do the following:
     * <br>
     * {@code
     *  ds.setConnectionAttributes("bootPassword=x8hhk2adf;upgrade=true");}
     *
     * See Derby documentation for complete list.
     */
    public void setConnectionAttributes(String prop) {
        connectionAttributes = prop;
    }

    /**
     * @return Derby specific connection URL attributes
     */
    public String getConnectionAttributes() {
        return connectionAttributes;
    }


    /**
     * @serial
     */
    private int traceLevel = propertyDefault_traceLevel;

    /**
     * Check if derby.client.traceLevel is provided as a JVM property.
     * If yes, then we use that value. If not, then we look for traceLevel
     * in the the properties parameter.
     *
     * @param properties jdbc url properties
     * @return value of traceLevel property
     */
    static int getTraceLevel(Properties properties) {
        String traceLevelString;
        traceLevelString  =
            readSystemProperty(Attribute.CLIENT_JVM_PROPERTY_PREFIX +
                               Attribute.CLIENT_TRACE_LEVEL);
        if (traceLevelString == null  && properties != null) {
            traceLevelString =
                properties.getProperty(Attribute.CLIENT_TRACE_LEVEL);
        }
        if (traceLevelString != null ) {
            return parseInt(traceLevelString, propertyDefault_traceLevel);
        } else {
            return propertyDefault_traceLevel;
        }
    }

    synchronized public void setTraceLevel(int traceLevel) {
        this.traceLevel = traceLevel;
    }

    public int getTraceLevel() {
        return this.traceLevel;
    }

    public synchronized void setTraceFile(String traceFile) {
        this.traceFile = traceFile;
    }

    public String getTraceFile() {
        return this.traceFile;
    }


    public synchronized void setTraceDirectory(String traceDirectory) {
        this.traceDirectory = traceDirectory;
    }

    public String getTraceDirectory() {
        return this.traceDirectory;
    }

    synchronized public void setTraceFileAppend(boolean traceFileAppend) {
        this.traceFileAppend = traceFileAppend;
    }

    public boolean getTraceFileAppend() {
        return this.traceFileAppend;
    }

    /**
     * Returns the maximum number of JDBC prepared statements a connection is
     * allowed to cache.
     * <p>
     * A basic data source will always return zero. If statement caching is
     * required, use a {@link javax.sql.ConnectionPoolDataSource}.
     * <p>
     * This method is used internally by Derby to determine if statement
     * pooling is to be enabled or not.
     * Not part of public API, so not present in
     * {@link org.apache.derby.jdbc.ClientDataSourceInterface}.
     *
     * @return Maximum number of statements to cache, or <code>0</code> if
     *      caching is disabled (default).
     */
    public int maxStatementsToPool() {
        return 0;
    }

    // --- private helper methods


    /**
     * The dataSource keeps individual fields for the values that are relevant
     * to the client. These need to be updated when set connection attributes
     * is called.
     */
    private void updateDataSourceValues(Properties prop)
        throws SqlException
    {
        // DERBY-5553. System properties derby.client.traceDirectory
        // and derby.client.traceLevel do not work for ClientXADataSource
        // or ClientConnectionPoolDataSource
        // Trace level and trace directory will be read from system
        // properties if they are not specified in the Properties
        // argument, so we check for them first to avoid getting cut
        // off by the (prop == null) check below.
        String traceDir = getTraceDirectory(prop);
        if (traceDir != null) {
            setTraceDirectory(traceDir);
        }
        
        int traceLevel = getTraceLevel(prop);
        if (traceLevel != propertyDefault_traceLevel) {
            setTraceLevel(traceLevel);
        }
        if (prop == null) {
            return;
        }

        if (prop.containsKey(Attribute.USERNAME_ATTR)) {
            setUser(getUser(prop));
        }
        if (prop.containsKey(Attribute.CLIENT_SECURITY_MECHANISM)) {
            setSecurityMechanism(getSecurityMechanism(prop));
        }
        if (prop.containsKey(Attribute.CLIENT_TRACE_FILE)) {
            setTraceFile(getTraceFile(prop));
        }
        if (prop.containsKey(Attribute.CLIENT_TRACE_APPEND)) {
            setTraceFileAppend(getTraceFileAppend(prop));
        }
        if (prop.containsKey(Attribute.CLIENT_RETIEVE_MESSAGE_TEXT)) {
            setRetrieveMessageText(getRetrieveMessageText(prop));
        }
        if (prop.containsKey(Attribute.SSL_ATTR)) {
            sslMode = getClientSSLMode(prop);
        }
    }

    /**
     * Handles common error situations that can happen when trying to
     * obtain a physical connection to the server, and which require special
     * handling.
     * <p>
     * If this method returns normally, the exception wasn't handled and should
     * be handled elsewhere or be re-thrown.
     *
     * @param logWriter log writer, may be {@code null}
     * @param sqle exception to handle
     * @throws SQLException handled exception (if any)
     */
    private void handleConnectionException(LogWriter logWriter,
                                                   SqlException sqle)
            throws SQLException {
        // See DERBY-4070
        if (sqle.getSQLState().equals(
                ExceptionUtil.getSQLStateFromIdentifier(
                    SQLState.INVALID_ATTRIBUTE_SYNTAX))) {
            // Wrap this in SQLState.MALFORMED_URL exception to be
            // consistent with the embedded driver.
            throw new SqlException(logWriter,
                    new ClientMessageId(SQLState.MALFORMED_URL),
                    sqle, constructUrl()).getSQLException();

        }
    }

    /**
     * Constructs the JDBC connection URL from the state of the data source.
     *
     * @return The JDBC connection URL.
     */
    private String constructUrl() {
        StringBuilder sb = new StringBuilder(64);
        // To support subSubProtocols, the protocol addition below must be
        // changed.
        sb.append(Attribute.DNC_PROTOCOL);
        sb.append(serverName);
        sb.append(':');
        sb.append(portNumber);
        sb.append('/');
        sb.append(databaseName);
        if (connectionAttributes != null) {
            sb.append(';');
            sb.append(connectionAttributes);
        }
        return sb.toString();
    }

    /**
     * Attempt to establish a database connection in a non-pooling,
     * non-distributed environment.
     *
     * @return a Connection to the database
     *
     * @throws java.sql.SQLException if a database-access error occurs.
     */
    public Connection getConnection() throws SQLException {
        LogWriter dncLogWriter = null;
        try {
            updateDataSourceValues(
                    tokenizeAttributes(getConnectionAttributes(), null));
            dncLogWriter = computeDncLogWriterForNewConnection("_sds");
            return getConnectionX(dncLogWriter, getUser(), getPassword());
        } catch (SqlException se) {
            // The method below may throw an exception.
            handleConnectionException(dncLogWriter, se);
            // If the exception wasn't handled so far, re-throw it.
            throw se.getSQLException();
        }
    }

    /**
     * Attempt to establish a database connection in a non-pooling,
     * non-distributed environment.
     *
     * @param user the database user on whose behalf the Connection is being
     *        made
     * @param password the user's password
     *
     * @return a Connection to the database
     *
     * @throws java.sql.SQLException if a database-access error occurs.
     */
    public Connection getConnection(String user, String password)
            throws SQLException {
        // Jdbc 2 connections will write driver trace info on a
        // datasource-wide basis using the jdbc 2 data source log writer.
        // This log writer may be narrowed to the connection-level
        // This log writer will be passed to the agent constructor.

        LogWriter dncLogWriter = null;
        try
        {
            updateDataSourceValues(
                    tokenizeAttributes(getConnectionAttributes(), null));
            dncLogWriter = computeDncLogWriterForNewConnection("_sds");
            return getConnectionX(dncLogWriter, user, password);
        }
        catch(SqlException se)
        {
            // The method below may throw an exception.
            handleConnectionException(dncLogWriter, se);
            // If the exception wasn't handled so far, re-throw it.
            throw se.getSQLException();
        }

    }

    private Connection getConnectionX(LogWriter dncLogWriter,
                                      String user, String password)
            throws SqlException {
        return ClientDriver.getFactory().newNetConnection(
                dncLogWriter, user, password, this, -1, false);

    }

    // JDBC 4.0 java.sql.Wrapper interface methods

    /**
     * Check whether this instance wraps an object that implements the
     * interface specified by {@code iface}.
     *
     * @param iface a class defining an interface
     * @return {@code true} if this instance implements {@code iface}, or
     * {@code false} otherwise
     * @throws SQLException if an error occurs while determining if this
     * instance implements {@code iface}
     */
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return iface.isInstance(this);
    }

    /**
     * Returns {@code this} if this class implements the specified interface.
     *
     * @param  iface a class defining an interface
     * @return an object that implements the interface
     * @throws SQLException if no object is found that implements the
     * interface
     */
    public <T> T unwrap(Class<T> iface) throws SQLException {
        try {
            return iface.cast(this);
        } catch (ClassCastException cce) {
            throw new SqlException(null,
                    new ClientMessageId(SQLState.UNABLE_TO_UNWRAP),
                    iface).getSQLException();
        }
    }

    ////////////////////////////////////////////////////////////////////
    //
    // INTRODUCED BY JDBC 4.1 IN JAVA 7
    //
    ////////////////////////////////////////////////////////////////////

    public Logger getParentLogger()
            throws SQLFeatureNotSupportedException {
        throw SQLExceptionFactory.notImplemented("getParentLogger");
    }

    // Helper methods

    protected final PooledConnection getPooledConnectionMinion()
            throws SQLException {
        LogWriter dncLogWriter = null;

        try {
            updateDataSourceValues(
                    tokenizeAttributes(getConnectionAttributes(), null));
            dncLogWriter = computeDncLogWriterForNewConnection("_cpds");

            if (dncLogWriter != null) {
                dncLogWriter.traceEntry(this, "getPooledConnection");
            }

            PooledConnection pooledConnection = getPooledConnectionX(
                    dncLogWriter, this, getUser(), getPassword());

            if (dncLogWriter != null) {
                dncLogWriter.traceExit(
                        this, "getPooledConnection", pooledConnection);
            }

            return pooledConnection;
        }
        catch (SqlException se) {
            // The method below may throw an exception.
            handleConnectionException(dncLogWriter, se);
            // If the exception wasn't handled so far, re-throw it.
            throw se.getSQLException();
        }
    }

    protected final PooledConnection getPooledConnectionMinion(
            String user, String password) throws SQLException {

        LogWriter dncLogWriter = null;

        try {
            updateDataSourceValues(
                    tokenizeAttributes(getConnectionAttributes(), null));
            dncLogWriter = computeDncLogWriterForNewConnection("_cpds");

            if (dncLogWriter != null) {
                dncLogWriter.traceEntry(
                        this, "getPooledConnection", user, "<escaped>");
            }

            PooledConnection pooledConnection = getPooledConnectionX(
                    dncLogWriter, this, user, password);

            if (dncLogWriter != null) {
                dncLogWriter.traceExit(
                        this, "getPooledConnection", pooledConnection);
            }

            return pooledConnection;

        } catch (SqlException se) {
            // The method below may throw an exception.
            handleConnectionException(dncLogWriter, se);
            // If the exception wasn't handled so far, re-throw it.
            throw se.getSQLException();
        }
    }

    // Minion method that establishes the initial physical connection
    // using DS properties instead of CPDS properties.
    private static PooledConnection getPooledConnectionX(
            LogWriter dncLogWriter,
            BasicClientDataSource40 ds,
            String user,
            String password) throws SQLException {

            return ClientDriver.getFactory().newClientPooledConnection(ds,
                    dncLogWriter, user, password);
    }

    protected final XAConnection getXAConnectionMinion() throws SQLException {
        LogWriter dncLogWriter = null;
        try {
            updateDataSourceValues(
                    tokenizeAttributes(getConnectionAttributes(), null));
            dncLogWriter = computeDncLogWriterForNewConnection("_xads");
            return getXAConnectionX(
                    dncLogWriter, this, getUser(), getPassword());
        } catch (SqlException se) {
            // The method below may throw an exception.
            handleConnectionException(dncLogWriter, se);
            // If the exception wasn't handled so far, re-throw it.
            throw se.getSQLException();
        }
    }

    protected final XAConnection getXAConnectionMinion(
            String user, String password) throws SQLException {

        LogWriter dncLogWriter = null;
        try
        {
            updateDataSourceValues(
                    tokenizeAttributes(getConnectionAttributes(), null));
            dncLogWriter = computeDncLogWriterForNewConnection("_xads");
            return getXAConnectionX(dncLogWriter, this, user, password);
        }
        catch ( SqlException se )
        {
            // The method below may throw an exception.
            handleConnectionException(dncLogWriter, se);
            // If the exception wasn't handled so far, re-throw it.
            throw se.getSQLException();
        }
    }

    /**
     * Method that establishes the initial physical connection
     * using DS properties instead of CPDS properties.
     */
    private static XAConnection getXAConnectionX(LogWriter dncLogWriter,
        BasicClientDataSource40 ds, String user, String password)
        throws SQLException
    {
        return ClientDriver.getFactory().newClientXAConnection(ds,
                dncLogWriter, user, password);
    }

    public static Properties getProperties(BasicClientDataSource40 ths) {
        Properties properties = new Properties();

        // Look for all the getXXX methods in the class that take no arguments.
        Method[] methods = ths.getClass().getMethods();

        for (int i = 0; i < methods.length; i++) {

            Method m = methods[i];

            // only look for simple getter methods.
            if (m.getParameterTypes().length != 0) {
                continue;
            }

            // only non-static methods
            if (Modifier.isStatic(m.getModifiers())) {
                continue;
            }

            // Only getXXX methods
            String methodName = m.getName();
            if ((methodName.length() < 5) || !methodName.startsWith("get")) {
                continue;
            }

            Class returnType = m.getReturnType();

            if (Integer.TYPE.equals(returnType)
                    || Short.TYPE.equals(returnType)
                    || String.class.equals(returnType)
                    || Boolean.TYPE.equals(returnType)) {

                // E.g. "getSomeProperty"
                //          s                 to lower case (3,4)
                //           omeProperty      use as is (4->)
                String propertyName = methodName.substring(3, 4).toLowerCase(
                        Locale.ENGLISH).concat(
                        methodName.substring(4));

                try {
                    Object ov = m.invoke(ths, (Object[])null);
                    // Need to check if property value is null, otherwise
                    // "null" string gets stored.
                    if (ov != null) {
                        properties.setProperty(propertyName, ov.toString());
                    }
                } catch (IllegalAccessException iae) {
                } catch (InvocationTargetException ite) {
                }

            }
        }

        return properties;
    }
}
