File: DatabasePropertyTestSetup.java

package info (click to toggle)
derby 10.14.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 78,740 kB
  • sloc: java: 691,931; sql: 42,686; xml: 20,511; sh: 3,373; sed: 96; makefile: 46
file content (465 lines) | stat: -rw-r--r-- 18,432 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
/*
 *
 * Derby - Class org.apache.derbyTesting.junit.DatabasePropertyTestSetup
 *
 * 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.derbyTesting.junit;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Properties;

import junit.framework.Test;

import org.apache.derbyTesting.functionTests.util.SQLStateConstants;

/**
 * Test decorator to set a set of database properties on setUp
 * and restore them to the previous values on tearDown.
 *
 */
public class DatabasePropertyTestSetup extends BaseJDBCTestSetup {
	
	private Properties newValues;
	private Properties oldValues;
    private final boolean staticProperties;
    
    /**
     * Decorator to change the lock time outs.
     * If either time is less than zero then that property is
     * not modified by this decorator.
     * The change is implemented by an instanceof DatabasePropertyTestSetup
     * and thus is reset by the tearDown method.
     * 
     * @param test Test to decorate
     * @param deadlockTime Time in seconds for derby.locks.deadlockTimeout.
     * @param waitTime Time in seconds for derby.locks.waitTimeout
     */
    public static Test setLockTimeouts(Test test, int deadlockTime, int waitTime)
    {
        final Properties properties = new Properties();
        if (deadlockTime >= 0)
        {
            properties.setProperty("derby.locks.deadlockTimeout",
                Integer.toString(deadlockTime));
        }
        if (waitTime >= 0) {
            properties.setProperty("derby.locks.waitTimeout",
                Integer.toString(waitTime));
        }
        
        // No change, no point to the decorator.
        if (properties.isEmpty())
            return test;

        return new DatabasePropertyTestSetup(test, properties, true);
    }
    
    /**
     * Decorate a test so that the database has authentication enabled
     * using the BUILTIN provider and the set of users passed in.
     * The password for each user is set to the user's name as set
     * in the users array with the value of passwordToken appended.
     * <BR>
     * The user names in the users array are treated as SQL identifiers
     * since that is the defined behaviour for derby.user.username.
     * This means that the quoted identifiers can be passed, examples
     * are (users[i] is shown as the contents of the Java string) with
     * a password suffix of T63:
     * <UL>
     * <LI>users[i]=fred - normal user name FRED, password fredT63
     * <LI>users[i]=FRED - normal user name FRED, passeword FREDT63
     * <LI>users[i]="FRED" - normal user name FRED, passeword "FREDT63"
     * <LI>users[i]="fred" - normal user name fred, passeword "fredT63"
     * </UL>
     * Thus with a quoted identifier the password will include the quotes.
     * Note bug DERBY-3150 exists which means that the normalized user name
     * to password mapping does not exist, thus a connection request must be
     * made with the values passed in the users array, not any other form of the
     * name.
     * <BR>
     * The decorated test can use BaseJDBCTestCase.openUserConnection(String user)
     * method to simplify using authentication.
     * <P>
     * Assumption is that no authentication was enabled upon entry.
     * <P>
     * Current user is set to the first user in the list users[0].
     * <P>
     * The authentication is removed by the decorator's tearDown method.
     * @param test Test to be decorated.
     * @param users Set of users for authentication.
     * @return Decorated test.
     */
    public static Test builtinAuthentication(Test test, String[] users,
            String passwordToken)
    {
        final Properties userProps = new Properties();
        final Properties authProps = new Properties();
        
        authProps.setProperty("derby.connection.requireAuthentication", "true");
        authProps.setProperty("derby.authentication.provider", "BUILTIN");
        
        for (int i = 0; i < users.length; i++)
        {
            String user = users[i];
            userProps.setProperty("derby.user." + user,
                    TestConfiguration.getPassword(user, passwordToken));
        }
        
        // Need to setup the decorators carefully.
        // Need execution in this order:
        // 1) add user definitions (no authentication enabled)
        // 2) switch to a valid user
        // 3) enable authentication with database reboot
        // 4) disable authentication with database reboot
        // 5) switch back to previous user
        // 6) remove user defintions.
        //
        // Combining steps 1,3 does not work as no shutdown request
        // is possible for step 4 as no valid users would be defined!
        //
        // Note the decorators are executed in order from
        // outer (added last) to inner.
        
        test = new DatabasePropertyTestSetup(test, authProps, true);
        test = new ChangeUserSetup(test, users[0],
                TestConfiguration.getPassword(users[0], passwordToken),
                passwordToken);
        test = new DatabasePropertyTestSetup(test, userProps, false);
        
        return test;
    }
    
    /**
     * Decorate a test so that the database has authentication enabled
     * using the BUILTIN provider and the set of users passed in.
     * The password for each user is set to the user's name with
     * the value of passwordToken appended.
     * <BR>
     * The decorated test can use BaseJDBCTestCase.openUserConnection(String user)
     * method to simplify using authentication.
     * <P>
     * Assumption is that no authentication was enabled upon entry.
     * <P>
     * Current user is set to the first user in the list users[0].
     * <P>
     * In contrast to plain builtinAuthentication, here the
     * authentication nor users are *NOT* removed by the decorator's
     * tearDown method.
     * @param test Test to be decorated.
     * @param users Set of users for authentication.
     * @return Decorated test.
     */
    public static Test builtinAuthenticationNoTeardown(Test test, String[] users,
            String passwordToken)
    {
        final Properties userProps = new Properties();
        final Properties authProps = new Properties();

        authProps.setProperty("derby.connection.requireAuthentication", "true");
        authProps.setProperty("derby.authentication.provider", "BUILTIN");

        for (int i = 0; i < users.length; i++)
        {
            String user = users[i];
            userProps.setProperty("derby.user." + user,
                    TestConfiguration.getPassword(user, passwordToken));
        }

        test = getNoTeardownInstance(test, authProps, true);
        test = new ChangeUserSetup(test, users[0],
                TestConfiguration.getPassword(users[0], passwordToken),
                passwordToken);
        test = getNoTeardownInstance(test, userProps, false);
        return test;
    }

    static DatabasePropertyTestSetup getNoTeardownInstance(
        Test test, Properties p, boolean staticp)
    {
        return new DatabasePropertyTestSetup(test, p, staticp) {
                protected void tearDown()
                        throws java.lang.Exception {
                    // We don't want to reset the properties, but we should
                    // still clear the reference to the default connection to
                    // allow it to be garbage collected.
                    clearConnection();
                }
            };
    }

    /**
     * Decorate a test so that it sets a single database property
     * at setUp and resets it at tearDown. Shorthand for
     * using DatabasePropertyTestSetup when only a single property is needed.
     * Does not perform a reboot of the database.
     */
    public static Test singleProperty(Test test, String property, String value)
    {
        return singleProperty(test, property, value, false);
    }
    /**
     * Decorate a test so that it sets a single database property
     * at setUp and resets it at tearDown. Shorthand for
     * using DatabasePropertyTestSetup when only a single property is needed.
     * Optinally reboots the database.
     */
    public static Test singleProperty(Test test, String property, String value,
            boolean reboot)
    {
        final Properties properties = new Properties();
        properties.setProperty(property, value);

        return new DatabasePropertyTestSetup(test, properties, reboot);
    }    
	
	/**
	 * Create a test decorator that sets and restores the passed
	 * in properties. Assumption is that the contents of
	 * properties and values will not change during execution.
	 * @param test test to be decorated
	 * @param newValues properties to be set
	 */
	public DatabasePropertyTestSetup(Test test,
			Properties newValues)
	{
        this(test, newValues, false);
    }
    
    /**
     * Create a test decorator that sets and restores the passed
     * in properties. Assumption is that the contents of
     * properties and values will not change during execution.
     * @param test test to be decorated
     * @param newValues properties to be set
     * @param staticProperties True if database needs to be shutdown after
     * setting properties in setUp() and tearDown method().
     */
    public DatabasePropertyTestSetup(Test test,
            Properties newValues, boolean staticProperties)
    {
		super(test);
		this.newValues = newValues;
		this.oldValues = new Properties();
        this.staticProperties = staticProperties;
	}

	/**
	 * For each property store the current value and
	 * replace it with the new value, unless there is no change.
	 */
    protected void setUp()
    throws java.lang.Exception
    {
    	setProperties(newValues);
        if (staticProperties) {
            TestConfiguration.getCurrent().shutdownDatabase();
        }
    }

    /**
     * Revert the properties to their values prior to the setUp call.
     */
    protected void tearDown()
    throws java.lang.Exception
    {
        Connection conn = getConnection();
        try {
            clearProperties(conn);
        } catch (SQLException sqle) {
            // To try to prevent the error situation of DERBY-5686, which
            // cascades to many test failures, catch ERROR 25502, and if it occurs
            // try to gather some information, close the connection,
            // and retry the clearing of the properties on a new connection
            if (sqle.getSQLState().equals("25502")) {
                // firstly, check on the state of the connection when we
                // get this error
                System.out.println("Apparently this is a read-only connection in teardown()? Get some data:");
                System.out.println("conn.isClosed: " + conn.isClosed());
                System.out.println("conn.isReadOnly: " + conn.isReadOnly());
                System.out.println("conn.getHoldability: " + conn.getHoldability());
                System.out.println("conn.getTransactionIsolation: " + conn.getTransactionIsolation());
                System.out.println("conn.getAutoCommit: " + conn.getAutoCommit());
                // now try to close the connection, then try open a new one, 
                // and try to executeUpdate again.
                try {
                    conn.close();
                } catch (SQLException isqle) {
                    if (sqle.getSQLState()=="25001")
                    {
                        // the transaction is still active. let's commit what we have.
                        conn.commit();
                        conn.close();
                    } else {
                        System.out.println("close failed - see SQLState.");
                        throw sqle;
                    }
                }
                Connection conn2 = getConnection();
                // check if this second connection is read-only
                if (conn2.isReadOnly())
                {
                    System.out.println("Sorry, conn2 is also read-only, won't retry");
                    // give up
                    throw sqle;
                }
                else
                {   
                    // retry
                    System.out.println("retrying clearing the Properties");
                    clearProperties(conn2);
                }
            }
            else if(!sqle.getSQLState().equals(SQLStateConstants.PROPERTY_UNSUPPORTED_CHANGE))
        		throw sqle;
        }
    	// and then reset nay old values which will cause the commit.
    	setProperties(oldValues);
        super.tearDown();
        newValues = null;
        oldValues = null;
        if (staticProperties) {
            TestConfiguration.getCurrent().shutdownDatabase();
        }
    }
    
    private void clearProperties(Connection conn) throws SQLException
    {
        conn.setAutoCommit(false);
        CallableStatement setDBP =  conn.prepareCall(
            "CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY(?, NULL)");
        // Clear all the system properties set by the new set
        // that will not be reset by the old set. Ignore any 
        // invalid property values.
        for (Enumeration e = newValues.propertyNames(); e.hasMoreElements();)
        {
            String key = (String) e.nextElement();
            if (oldValues.getProperty(key) == null)
            {
                setDBP.setString(1, key);
                setDBP.executeUpdate();
            }
        }
    }

    private void setProperties(Properties values) throws SQLException
    {
        Connection conn = getConnection();
        try {
            attemptSetProperties(values, conn);
        } catch (SQLException sqle) {
            // To try to prevent the error situation of DERBY-5686, which
            // cascades to many test failures, catch ERROR 25502, and if it occurs
            // try to gather some information, close the connection,
            // and retry the clearing of the properties on a new connection
            if (sqle.getSQLState().equals("25502")) {
                // firstly, check on the state of the connection when we
                // get this error
                System.out.println("Apparently this is a read-only connection? Get some data:");
                System.out.println("conn.isClosed: " + conn.isClosed());
                System.out.println("conn.isReadOnly: " + conn.isReadOnly());
                System.out.println("conn.getHoldability: " + conn.getHoldability());
                System.out.println("conn.getTransactionIsolation: " + conn.getTransactionIsolation());
                System.out.println("conn.getAutoCommit: " + conn.getAutoCommit());
                // now try to close the connection, then try open a new one, 
                // and try to executeUpdate again.
                try {
                    conn.close();
                } catch (SQLException isqle) {
                    if (sqle.getSQLState()=="25001")
                    {
                        // the transaction is still active. let's commit what we have.
                        conn.commit();
                        conn.close();
                    } else {
                        System.out.println("close failed - see SQLState.");
                        throw sqle;
                    }
                }
                Connection conn2 = getConnection();

                // check if this second connection is read-only
                if (conn2.isReadOnly())
                {
                    System.out.println("Sorry, conn2 is also read-only, won't retry");
                    // give up
                    throw sqle;
                }
                else
                {   
                    // retry
                    System.out.println("retrying to set the Properties");
                    attemptSetProperties(values, conn2);
                }
            }
        }
    }
    
    private void attemptSetProperties(Properties values, Connection coonn) throws SQLException
    {
        Connection conn = getConnection();
        conn.setAutoCommit(false);
        
        PreparedStatement getDBP =  conn.prepareStatement(
            "VALUES SYSCS_UTIL.SYSCS_GET_DATABASE_PROPERTY(?)");
        CallableStatement setDBP =  conn.prepareCall(
            "CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY(?, ?)");
        
        
    	for (Enumeration e = values.propertyNames(); e.hasMoreElements();)
    	{
    		final String key = (String) e.nextElement();
    		final String value = values.getProperty(key);
            
            getDBP.setString(1, key);
            ResultSet rs = getDBP.executeQuery();
            rs.next();
            String old = rs.getString(1);
            rs.close();
                		
    		boolean change;
    		if (old != null)
    		{
                // set, might need to be changed.
                change = !old.equals(value);
                
                // If we are not processing the oldValues
                // then store in the oldValues. Reference equality is ok here.
    			if (change && (values != oldValues))
    			   oldValues.setProperty(key, old);
    		}
    		else {
    			// notset, needs to be set
    			change = true;
    		}
    		
    		if (change) {
                setDBP.setString(1, key);
                setDBP.setString(2, value);
                setDBP.executeUpdate();
   		    }
    	}
        conn.commit();
        getDBP.close();
        setDBP.close();
        conn.close();
    }
}