You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by ka...@apache.org on 2014/01/06 11:05:36 UTC

svn commit: r1555702 - in /db/derby/code/trunk/java: engine/org/apache/derby/iapi/sql/dictionary/ engine/org/apache/derby/impl/sql/compile/ engine/org/apache/derby/impl/sql/execute/ testing/org/apache/derbyTesting/functionTests/tests/lang/ testing/org/...

Author: kahatlen
Date: Mon Jan  6 10:05:35 2014
New Revision: 1555702

URL: http://svn.apache.org/r1555702
Log:
DERBY-5866: Triggers fire out of order

Make sure the creation timestamp of a newly created trigger is higher
than the timestamps of the existing triggers on the same table. If it
is not higher, the triggers may fire in the wrong order. If the
current timestamp is not higher than the timestamp of the previous
trigger defined on the table, the new trigger now gets a creation
timestamp that is one millisecond higher than the creation timestamp
of the previous trigger.

Added:
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/Derby5866TriggerOrderTest.java   (with props)
Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/CreateTriggerNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CreateTriggerConstantAction.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/GenericConstantActionFactory.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/_Suite.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/TimeZoneTestSetup.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java?rev=1555702&r1=1555701&r2=1555702&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java Mon Jan  6 10:05:35 2014
@@ -1335,8 +1335,9 @@ public interface DataDictionary
 	/**
 	 * Load up the trigger descriptor list for this table
 	 * descriptor and return it.  If the descriptor list
-	 * is already loaded up, it is retuned without further
-	 * ado.
+     * is already loaded up, it is returned without further
+     * ado. The descriptors are returned in the order in
+     * which the triggers were created, with the oldest first.
 	 *
 	 * @param td			The table descriptor.
 	 *

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/CreateTriggerNode.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/CreateTriggerNode.java?rev=1555702&r1=1555701&r2=1555702&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/CreateTriggerNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/CreateTriggerNode.java Mon Jan  6 10:05:35 2014
@@ -943,7 +943,6 @@ class CreateTriggerNode extends DDLState
 											(UUID)null,			// action SPSid 
 											actionText,
                                             compSchemaDescriptor.getUUID(),
-											(Timestamp)null,	// creation time
 											referencedColInts,
 											referencedColsInTriggerAction,
                                             originalWhenText,

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CreateTriggerConstantAction.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CreateTriggerConstantAction.java?rev=1555702&r1=1555701&r2=1555702&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CreateTriggerConstantAction.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CreateTriggerConstantAction.java Mon Jan  6 10:05:35 2014
@@ -21,6 +21,7 @@
 
 package org.apache.derby.impl.sql.execute;
 
+import org.apache.derby.iapi.services.property.PropertyUtil;
 import org.apache.derby.iapi.store.access.TransactionController;
 
 import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
@@ -33,6 +34,7 @@ import org.apache.derby.iapi.sql.diction
 import org.apache.derby.iapi.sql.dictionary.SPSDescriptor;
 import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
 import org.apache.derby.iapi.sql.dictionary.TriggerDescriptor;
+import org.apache.derby.iapi.sql.dictionary.TriggerDescriptorList;
 
 import org.apache.derby.iapi.sql.depend.DependencyManager;
 import org.apache.derby.iapi.sql.depend.Provider;
@@ -76,7 +78,6 @@ class CreateTriggerConstantAction extend
 	private String					oldReferencingName;
 	private String					newReferencingName;
 	private UUID					spsCompSchemaId;
-	private Timestamp				creationTimestamp;
 	private int[]					referencedCols;
 	private int[]					referencedColsInTriggerAction;
     private final ProviderInfo[]    providerInfo;
@@ -100,8 +101,6 @@ class CreateTriggerConstantAction extend
 	 * @param spsCompSchemaId	the compilation schema for the action and when
 	 *							spses.   If null, will be set to the current default
 	 *							schema
-	 * @param creationTimestamp	when was this trigger created?  if null, will be
-	 *						set to the time that executeConstantAction() is invoked
 	 * @param referencedCols	what columns does this trigger reference (may be null)
 	 * @param referencedColsInTriggerAction	what columns does the trigger 
 	 *						action reference through old/new transition variables
@@ -128,7 +127,6 @@ class CreateTriggerConstantAction extend
 		UUID				actionSPSId,
 		String				actionText,
 		UUID				spsCompSchemaId,
-		Timestamp			creationTimestamp,
 		int[]				referencedCols,
 		int[]				referencedColsInTriggerAction,
         String              originalWhenText,
@@ -153,7 +151,6 @@ class CreateTriggerConstantAction extend
 		this.actionSPSId = actionSPSId;
 		this.actionText = actionText;
 		this.spsCompSchemaId = spsCompSchemaId;
-		this.creationTimestamp = creationTimestamp;
 		this.referencedCols = referencedCols;
 		this.referencedColsInTriggerAction = referencedColsInTriggerAction;
 		this.originalActionText = originalActionText;
@@ -316,7 +313,7 @@ class CreateTriggerConstantAction extend
 									triggerTable,
                                     whenSPSId,
 									actionSPSId,
-									creationTimestamp == null ? new Timestamp(System.currentTimeMillis()) : creationTimestamp,
+                                    makeCreationTimestamp(dd),
 									referencedCols,
 									referencedColsInTriggerAction,
 									originalActionText,
@@ -435,6 +432,53 @@ class CreateTriggerConstantAction extend
 	{
 		return constructToString("CREATE TRIGGER ", triggerName);		
 	}
-}
 
+    /**
+     * Construct the creation timestamp for the trigger. DERBY-5866: Also make
+     * sure the creation timestamp is higher than any timestamp on an existing
+     * trigger on the same table. Otherwise, the triggers may not fire in the
+     * correct order.
+     */
+    private Timestamp makeCreationTimestamp(DataDictionary dd)
+            throws StandardException {
+        Timestamp now = new Timestamp(System.currentTimeMillis());
+
+        // Allow overriding the timestamp in debug mode for testing of
+        // specific scenarios.
+        if (SanityManager.DEBUG) {
+            String val = PropertyUtil.getSystemProperty(
+                    "derby.debug.overrideTriggerCreationTimestamp");
+            if (val != null) {
+                now.setTime(Long.parseLong(val));
+            }
+        }
+
+        TriggerDescriptorList tdl = dd.getTriggerDescriptors(triggerTable);
+        int numTriggers = tdl.size();
+
+        if (numTriggers == 0) {
+            // This is the first trigger on the table, so no need to check
+            // if there are any higher timestamps.
+            return now;
+        }
+
+        // Get the timestamp of the most recent existing trigger on the table.
+        Timestamp highest = tdl.get(numTriggers - 1).getCreationTimestamp();
 
+        if (now.after(highest)) {
+            // The current timestamp is higher than the most recent existing
+            // trigger on the table, so it is OK.
+            return now;
+        }
+
+        // Otherwise, there is an existing trigger on the table with a
+        // timestamp that is at least as high as the current timestamp. Adjust
+        // the current timestamp so that it is one millisecond higher than the
+        // timestamp of the existing trigger. This ensures that the triggers
+        // will fire in the same order as they were created.
+
+        now.setTime(highest.getTime() + 1);
+
+        return now;
+    }
+}

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/GenericConstantActionFactory.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/GenericConstantActionFactory.java?rev=1555702&r1=1555701&r2=1555702&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/GenericConstantActionFactory.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/GenericConstantActionFactory.java Mon Jan  6 10:05:35 2014
@@ -1007,8 +1007,6 @@ public class GenericConstantActionFactor
 	 * @param spsCompSchemaId	the compilation schema for the action and when
 	 *							spses.   If null, will be set to the current default
 	 *							schema
-	 * @param creationTimestamp	when was this trigger created?  if null, will be
-	 *						set to the time that executeConstantAction() is invoked
 	 * @param referencedCols	what columns does this trigger reference (may be null)
 	 * @param referencedColsInTriggerAction	what columns does the trigger 
 	 *						action reference through old/new transition variables
@@ -1035,7 +1033,6 @@ public class GenericConstantActionFactor
 		UUID				actionSPSId,
 		String				actionText,
 		UUID				spsCompSchemaId,
-		Timestamp			creationTimestamp,
 		int[]				referencedCols,
 		int[]				referencedColsInTriggerAction,
         String              originalWhenText,
@@ -1049,7 +1046,7 @@ public class GenericConstantActionFactor
 	{
 		return new CreateTriggerConstantAction(triggerSchemaName, triggerName, 
 				eventMask, isBefore, isRow, isEnabled, triggerTable, whenSPSId,
-				whenText, actionSPSId, actionText, spsCompSchemaId, creationTimestamp,
+                whenText, actionSPSId, actionText, spsCompSchemaId,
                 referencedCols, referencedColsInTriggerAction,
                 originalWhenText, originalActionText,
                 referencingOld, referencingNew,

Added: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/Derby5866TriggerOrderTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/Derby5866TriggerOrderTest.java?rev=1555702&view=auto
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/Derby5866TriggerOrderTest.java (added)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/Derby5866TriggerOrderTest.java Mon Jan  6 10:05:35 2014
@@ -0,0 +1,267 @@
+/*
+ * Derby - Class org.apache.derbyTesting.functionTests.tests.lang.Derby5866TriggerOrderTest
+ *
+ * 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.functionTests.tests.lang;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Timestamp;
+import java.util.Calendar;
+import java.util.TimeZone;
+import junit.framework.Test;
+import org.apache.derbyTesting.junit.BaseJDBCTestCase;
+import org.apache.derbyTesting.junit.CleanDatabaseTestSetup;
+import org.apache.derbyTesting.junit.JDBC;
+import org.apache.derbyTesting.junit.TestConfiguration;
+import org.apache.derbyTesting.junit.TimeZoneTestSetup;
+
+/**
+ * <p>
+ * Test that triggers for a specific event execute in the order in which they
+ * were defined. This is a regression test case for DERBY-5866, where triggers
+ * were seen to fire in a nondeterministic order if the system clock was too
+ * coarse-grained and gave the triggers identical creation time stamps. It
+ * also tests that triggers fire in the correct order when the triggers are
+ * created in different time zones, or right before or after daylight saving.
+ * </p>
+ */
+public class Derby5866TriggerOrderTest extends BaseJDBCTestCase {
+
+    private final static TimeZone TIMEZONE =
+            TimeZone.getTimeZone("Europe/Oslo");
+
+    private final static String OVERRIDE_TIME_PROP =
+            "derby.debug.overrideTriggerCreationTimestamp";
+
+    public Derby5866TriggerOrderTest(String name) {
+        super(name);
+    }
+
+    public static Test suite() {
+        Test test = new CleanDatabaseTestSetup(
+            TestConfiguration.embeddedSuite(Derby5866TriggerOrderTest.class));
+        return new TimeZoneTestSetup(test, TIMEZONE);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Reset the time zone after each test case, since the test case
+        // may have altered it.
+        TimeZoneTestSetup.setDefault(TIMEZONE);
+
+        // Clear the system property that overrides the trigger creation
+        // timestamps.
+        removeSystemProperty(OVERRIDE_TIME_PROP);
+
+        super.tearDown();
+    }
+
+    /**
+     * Test that triggers fire in the correct order if the time zone changes
+     * between two CREATE TRIGGER operations in a way that makes it look like
+     * the second trigger was created before the first trigger.
+     */
+    public void testTimeZoneChange() throws SQLException {
+        setAutoCommit(false);
+
+        Statement s = createStatement();
+        s.execute("create table t1(x int)");
+        s.execute("create table t2(x int generated always as identity, "
+                + "y varchar(128))");
+
+        // Create the first trigger while in the GMT time zone.
+        s.execute("create trigger tr1 after insert on t1 "
+                + "insert into t2(y) values 'I won! :)'");
+
+        // Travel back in time. Sort of... At least that's how it's perceived
+        // until TIMESTAMP WITH TIMEZONE is supported, and SYSTRIGGERS is
+        // updated to use it (DERBY-5974).
+        TimeZoneTestSetup.setDefault(TimeZone.getTimeZone("GMT-8:00"));
+        s.execute("create trigger tr2 after insert on t1 "
+                + "insert into t2(y) values 'I lost... :('");
+
+        // Fire the triggers.
+        s.execute("insert into t1 values 1");
+
+        // Check which of the triggers was executed first. It should have been
+        // the trigger that was defined first. Before DERBY-5866, they fired
+        // in the opposite order.
+        JDBC.assertFullResultSet(s.executeQuery("select * from t2 order by x"),
+                                 new String[][] {
+                                     { "1", "I won! :)" },
+                                     { "2", "I lost... :(" },
+                                 });
+    }
+
+    /**
+     * Test that triggers fire in the correct order if the clock shows the
+     * same creation time for all the triggers.
+     */
+    public void testEqualTimestamps() throws SQLException {
+        Timestamp now = new Timestamp(System.currentTimeMillis());
+        testSpecificTimestamps(now, now, now);
+    }
+
+    /**
+     * Test that the triggers fire in creation order even if the clock goes
+     * backwards.
+     */
+    public void testReversedTimestamps() throws SQLException {
+        long now = System.currentTimeMillis();
+        testSpecificTimestamps(new Timestamp(now), new Timestamp(now - 1),
+                               new Timestamp(now - 2), new Timestamp(now - 3));
+    }
+
+    /**
+     * Test that triggers fire in the correct order if they are created around
+     * the daylight saving time switchover.
+     */
+    public void testCrossDaylightSaving() throws SQLException {
+        // Use a GMT-based calendar to prevent ambiguities. For example, with
+        // a CET-based calendar, it would be ambiguous whether 2014-10-26
+        // 02:45:00 means 2014-10-26 02:45:00 CET or 2014-10-26 02:45:00 CEST.
+        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+
+        // 15 min before Central European Time switches to DST.
+        cal.set(2014, Calendar.MARCH, 30, 0, 45, 0);
+
+        // Test that triggers are ordered correctly if they are created
+        // 15 min before switch and 15 min after switch.
+        testSpecificTimestamps(new Timestamp(cal.getTimeInMillis()),
+                               new Timestamp(cal.getTimeInMillis() + 1800000));
+
+        // 15 min before Central European Time switches from DST.
+        cal.clear();
+        cal.set(2014, Calendar.OCTOBER, 26, 0, 45, 0);
+
+        // Test that triggers are ordered correctly if they are created
+        // 15 min before switch and 15 min after switch.
+        testSpecificTimestamps(new Timestamp(cal.getTimeInMillis()),
+                               new Timestamp(cal.getTimeInMillis() + 1800000));
+
+        // Last millisecond before switch to DST.
+        cal.clear();
+        cal.set(2014, Calendar.MARCH, 30, 0, 59, 59);
+        cal.set(Calendar.MILLISECOND, 999);
+        Timestamp ts = new Timestamp(cal.getTimeInMillis());
+        testSpecificTimestamps(ts, ts, ts);
+
+        // Last millisecond before switch from DST.
+        cal.clear();
+        cal.set(2014, Calendar.OCTOBER, 26, 0, 59, 59);
+        cal.set(Calendar.MILLISECOND, 999);
+        ts = new Timestamp(cal.getTimeInMillis());
+        testSpecificTimestamps(ts, ts, ts);
+    }
+
+    /**
+     * Test that triggers created before the epoch (Jan 1 1970) fire in the
+     * correct order.
+     */
+    public void testPreEpoch() throws SQLException {
+        // 24 hours before the epoch
+        Timestamp ts = new Timestamp(-3600L * 24 * 1000);
+        testSpecificTimestamps(ts, ts, ts);
+
+        // Test with some non-zero fractions as well.
+
+        ts.setNanos(123000000);
+        testSpecificTimestamps(ts, ts, ts);
+
+        ts.setNanos(567000000);
+        testSpecificTimestamps(ts, ts, ts);
+
+        ts.setNanos(999000000);
+        testSpecificTimestamps(ts, ts, ts);
+    }
+
+    /**
+     * Helper method that creates triggers with the specified creation
+     * timestamps and verifies that they fire in creation order. The creation
+     * timestamps can only be overridden in debug builds. When running in a
+     * non-debug build, this method will simply create the triggers without
+     * overriding the creation timestamps, and verify that they fire in the
+     * expected order.
+     */
+    private void testSpecificTimestamps(Timestamp... timestamps)
+            throws SQLException {
+        setAutoCommit(false);
+
+        Statement s = createStatement();
+
+        s.execute("create table t1(x int)");
+        s.execute("create table t2(x int generated always as identity, y int)");
+
+        // Create the triggers.
+        for (int i = 0; i < timestamps.length; i++) {
+            overrideTriggerCreationTime(timestamps[i]);
+            s.execute("create trigger tr" + (i + 1) + " after insert on t1 "
+                    + "insert into t2(y) values " + (i + 1));
+        }
+
+        // Fire the triggers.
+        s.execute("insert into t1 values 1");
+
+        // Verify that the triggers executed in the correct order.
+        ResultSet rs = s.executeQuery("select * from t2 order by x");
+        for (int i = 1; i <= timestamps.length; i++) {
+            if (rs.next()) {
+                assertEquals("X", i, rs.getInt("X"));
+                assertEquals("Y", i, rs.getInt("Y"));
+            } else {
+                fail("Row " + i + " was missing");
+            }
+        }
+        JDBC.assertEmpty(rs);
+
+        // Verify that the CREATIONTIMESTAMP column in SYS.SYSTRIGGERS is
+        // monotonically increasing.
+        PreparedStatement ps = prepareStatement(
+                "select * from sys.sysschemas natural join sys.systriggers "
+                + "where schemaname = ? and triggername like 'TR%' "
+                + "order by creationtimestamp");
+        ps.setString(1, getTestConfiguration().getUserName());
+        rs = ps.executeQuery();
+        Timestamp prev = null;
+        for (int i = 1; i <= timestamps.length; i++) {
+            assertTrue(rs.next());
+            assertEquals("TR" + i, rs.getString("TRIGGERNAME"));
+            Timestamp ts = rs.getTimestamp("CREATIONTIMESTAMP");
+            assertNotNull(ts);
+            if (prev != null && !prev.before(ts)) {
+                fail(prev + " expected to be before " + ts);
+            }
+            prev = ts;
+        }
+        JDBC.assertEmpty(rs);
+
+        rollback();
+    }
+
+    /**
+     * Set a system property that makes the next CREATE TRIGGER operation
+     * use the specified timestamp instead of the current time when
+     * constructing the creation timestamp.
+     */
+    private void overrideTriggerCreationTime(Timestamp ts) {
+        setSystemProperty(OVERRIDE_TIME_PROP, String.valueOf(ts.getTime()));
+    }
+}

Propchange: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/Derby5866TriggerOrderTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/_Suite.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/_Suite.java?rev=1555702&r1=1555701&r2=1555702&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/_Suite.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/_Suite.java Mon Jan  6 10:05:35 2014
@@ -244,6 +244,7 @@ public class _Suite extends BaseTestCase
         suite.addTest(MergeStatementTest.suite());
         suite.addTest(ConstraintCharacteristicsTest.suite());
         suite.addTest(DB2IsolationLevelsTest.suite());
+        suite.addTest(Derby5866TriggerOrderTest.suite());
         return suite;
 	}
 }

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/TimeZoneTestSetup.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/TimeZoneTestSetup.java?rev=1555702&r1=1555701&r2=1555702&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/TimeZoneTestSetup.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/TimeZoneTestSetup.java Mon Jan  6 10:05:35 2014
@@ -70,13 +70,13 @@ public class TimeZoneTestSetup extends B
         requestedDefault = null;
     }
     
-    private void setDefault(final TimeZone tz) throws SecurityException{
+    public static void setDefault(final TimeZone tz) {
         if (tz== null) {
             throw new IllegalArgumentException("tz cannot be <null>");
         }
         AccessController.doPrivileged(
-                new PrivilegedAction<Object>() {
-                    public Object run() throws SecurityException {
+                new PrivilegedAction<Void>() {
+                    public Void run() {
                         TimeZone.setDefault(tz);
                         return null;
                     }});