1 /*
2  * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 import java.io.ByteArrayInputStream;
24 import java.io.ByteArrayOutputStream;
25 import java.io.IOException;
26 import java.io.ObjectInputStream;
27 import java.io.ObjectOutputStream;
28 import java.util.Base64;
29 import java.util.logging.Level;
30 import java.util.logging.LogRecord;
31 import java.util.logging.XMLFormatter;
32 import java.util.stream.Stream;
33 
34 /**
35  * @test
36  * @bug 8245302
37  * @summary tests the deprecation of threadID and a new field longThreadID,
38  * test should be run on jdk16 and subsequent versions
39  * @run main/othervm SerializeLogRecordTest
40  */
41 public class SerializeLogRecordTest {
42 
43     /**
44      * Serializes a log record, encode the serialized bytes in base 64, and
45      * prints pseudo java code that can be cut and pasted into this test.
46      * @param record the log record to serialize, encode in base 64, and for
47      *               which test data will be generated.
48      * @return A string containing the generated pseudo java code.
49      * @throws IOException Unexpected.
50      * @throws ClassNotFoundException  Unexpected.
51      */
generate(LogRecord record)52     public static String generate(LogRecord record) throws IOException, ClassNotFoundException {
53 
54         XMLFormatter formatter = new XMLFormatter();
55         String str = formatter.format(record);
56 
57         // Serialize the given LogRecord
58         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
59         final ObjectOutputStream oos = new ObjectOutputStream(baos);
60         oos.writeObject(record);
61         oos.flush();
62         oos.close();
63 
64         // Now we're going to perform a number of smoke tests before
65         // generating the Java pseudo code.
66         //
67         // First checks that the log record can be deserialized
68         final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
69         final ObjectInputStream ois = new ObjectInputStream(bais);
70         final LogRecord record2 = (LogRecord)ois.readObject();
71 
72         String str2 = formatter.format(record2);
73 
74         if (!str.equals(str2)) throw new RuntimeException("Unexpected values in deserialized object:"
75             + "\n\tExpected:  " + str
76             + "\n\tRetrieved: "+str);
77 
78         // Now get a Base64 string representation of the serialized bytes.
79         final String base64 = Base64.getEncoder().encodeToString(baos.toByteArray());
80 
81         // Check that we can deserialize a log record from the Base64 string
82         // representation we just computed.
83         final ByteArrayInputStream bais2 = new ByteArrayInputStream(Base64.getDecoder().decode(base64));
84         final ObjectInputStream ois2 = new ObjectInputStream(bais2);
85         final LogRecord record3 = (LogRecord)ois2.readObject();
86 
87         // Format the new deserialized LogRecord using the SimpleFormatter, and
88         // check that the string representation obtained matches the string
89         // representation of the original LogRecord
90         String str3 = formatter.format(record3);
91         if (!str.equals(str3)) throw new RuntimeException("Unexpected values in deserialized object:"
92             + "\n\tExpected:  " + str
93             + "\n\tRetrieved: "+str);
94 
95         // Generates the Java Pseudo code that can be cut & pasted into
96         // this test (see Jdk8SerializedLog and Jdk9SerializedLog below)
97         final StringBuilder sb = new StringBuilder();
98         sb.append("    /**").append('\n');
99         sb.append("     * Base64 encoded string for LogRecord object.").append('\n');
100         sb.append("     * Java version: ").append(System.getProperty("java.version")).append('\n');
101         sb.append("     * threadID: ").append(record.getThreadID()).append('\n');
102         sb.append("     * longThreadID: ").append(record.getLongThreadID()).append('\n');
103         sb.append("     **/").append('\n');
104         sb.append("    final String base64 = ").append("\n          ");
105         final int last = base64.length() - 1;
106         for (int i=0; i<base64.length();i++) {
107             if (i%64 == 0) sb.append("\"");
108             sb.append(base64.charAt(i));
109             if (i%64 == 63 || i == last) {
110                 sb.append("\"");
111                 if (i == last) sb.append(";\n");
112                 else sb.append("\n        + ");
113             }
114         }
115         return sb.toString();
116     }
117 
118     /**
119      * An abstract class to test that a log record previously serialized on a
120      * different java version can be deserialized in the current java version.
121      * (see Jdk11SerializedLog and Jdk16SerializedLog below)
122      */
123     public abstract static class SerializedLog {
getBase64()124         public abstract String getBase64();
125 
126         /**
127          * Deserializes the Base64 encoded string returned by {@link
128          * #getBase64()}, and checks that the string representation obtained
129          * matches the original string representation returned by
130          */
dotest()131         protected void dotest() {
132             try {
133                 final String base64 = getBase64();
134                 final ByteArrayInputStream bais =
135                         new ByteArrayInputStream(Base64.getDecoder().decode(base64));
136                 final ObjectInputStream ois = new ObjectInputStream(bais);
137                 final LogRecord record = (LogRecord)ois.readObject();
138                 check(record);
139                 System.out.println("PASSED: "+this.getClass().getName()+"\n");
140             } catch (IOException | ClassNotFoundException x) {
141                 throw new RuntimeException(x);
142             }
143         }
144         /**
145          * Check that the values of threadID and longThreadID
146          * are correct and consistent when de-serialized from
147          * earlier jdk versions(jdk11)
148          */
check(LogRecord r1)149         protected void check(LogRecord r1) {
150             XMLFormatter formatter = new XMLFormatter();
151             int check = Integer.MAX_VALUE - 10;
152             String str = formatter.format(r1);
153             String loggerName = r1.getLoggerName();
154 
155             if (loggerName.equals("test2")) {
156                 int id = -2147483639;
157                 if (r1.getLongThreadID() != Integer.MAX_VALUE + 10L || r1.getThreadID() != id) {
158                     throw new RuntimeException(this.getClass().getName());
159                 }
160                 else {
161                     System.out.println("Test Integer Check Passed");
162                 }
163 
164             }
165 
166             if (loggerName.equals("test1") || loggerName.equals("test")) {
167                 if (loggerName.equals("test1")) {
168                     check = Integer.MAX_VALUE / 2 - 20;
169                 }
170 
171                 int tid = r1.getThreadID();
172                 long longThreadID = r1.getLongThreadID();
173                 if (tid != check || longThreadID != check || !str.contains("<thread>" + String.valueOf(longThreadID))) {
174                     throw new RuntimeException(this.getClass().getName());
175                 }
176                 else {
177                     System.out.println("Test Integer Check Passed");
178                 }
179             }
180         }
181     }
182 
generate()183     public static void generate() {
184         try {
185             LogRecord record = new LogRecord(Level.INFO, "Java Version: {0}");
186             record.setLoggerName("test");
187             record.setThreadID(Integer.MAX_VALUE - 10);
188             record.setParameters(new Object[] {System.getProperty("java.version")});
189             System.out.println(generate(record));
190             LogRecord record1 = new LogRecord(Level.INFO, "Java Version: {0}");
191             record1.setLoggerName("test1");
192             record1.setLongThreadID(Integer.MAX_VALUE/2 - 20);
193             record1.setParameters(new Object[] {System.getProperty("java.version")});
194             System.out.println(generate(record1));
195             LogRecord record2 = new LogRecord(Level.INFO, "Java Version: {0}");
196             record2.setLoggerName("test2");
197             record2.setLongThreadID(Integer.MAX_VALUE + 10L);
198             record2.setParameters(new Object[] {System.getProperty("java.version")});
199             System.out.println(generate(record2));
200         } catch (IOException | ClassNotFoundException x) {
201             throw new RuntimeException(x);
202         }
203     }
204 
205     public static class Jdk11SerializedLog extends SerializedLog {
206 
207         /**
208          * Base64 encoded string for LogRecord object.
209          * It is generated using generate method and
210          * can be copy pasted as base64 value, for generating
211          * this value, execute this class with an argument value
212          * generate and the test will print out the base64 value
213          * that can be copy pasted below
214          * Java version: 11.0.6
215          **/
216         final String base64 =
217             "rO0ABXNyABtqYXZhLnV0aWwubG9nZ2luZy5Mb2dSZWNvcmRKjVk982lRlgMAC0oA"
218                 + "Bm1pbGxpc0kADm5hbm9BZGp1c3RtZW50SgAOc2VxdWVuY2VOdW1iZXJJAAh0aHJl"
219                 + "YWRJREwABWxldmVsdAAZTGphdmEvdXRpbC9sb2dnaW5nL0xldmVsO0wACmxvZ2dl"
220                 + "ck5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMAAdtZXNzYWdlcQB+AAJMABJyZXNv"
221                 + "dXJjZUJ1bmRsZU5hbWVxAH4AAkwAD3NvdXJjZUNsYXNzTmFtZXEAfgACTAAQc291"
222                 + "cmNlTWV0aG9kTmFtZXEAfgACTAAGdGhyb3dudAAVTGphdmEvbGFuZy9UaHJvd2Fi"
223                 + "bGU7eHAAAAFycRovVgAFN/AAAAAAAAAAAH////VzcgAXamF2YS51dGlsLmxvZ2dp"
224                 + "bmcuTGV2ZWyOiHETUXM2kgIAA0kABXZhbHVlTAAEbmFtZXEAfgACTAAScmVzb3Vy"
225                 + "Y2VCdW5kbGVOYW1lcQB+AAJ4cAAAAyB0AARJTkZPdAAic3VuLnV0aWwubG9nZ2lu"
226                 + "Zy5yZXNvdXJjZXMubG9nZ2luZ3QABHRlc3R0ABFKYXZhIFZlcnNpb246IHswfXBw"
227                 + "cHB3BgEAAAAAAXQABjExLjAuNng=";
228 
229 
230         @Override
getBase64()231         public String getBase64() {
232             return base64;
233         }
234 
235         @Override
check(LogRecord r1)236         protected void check(LogRecord r1) {
237             super.check(r1);
238         }
239 
test()240         public static void test() {
241             new Jdk11SerializedLog().dotest();
242         }
243     }
244 
245     public static class Jdk16SerializedLog extends SerializedLog {
246 
247         /**
248          * Base64 encoded string for LogRecord object.
249          * It is generated using generate method and
250          * can be copy pasted as base64 value, for generating
251          * this value, execute this class with an argument value
252          * generate and the test will print out the base64 value
253          * that can be copy pasted below
254          * Java version: 16-internal
255          **/
256         final String base64 =
257             "rO0ABXNyABtqYXZhLnV0aWwubG9nZ2luZy5Mb2dSZWNvcmRKjVk982lRlgMADEoA"
258                 + "DGxvbmdUaHJlYWRJREoABm1pbGxpc0kADm5hbm9BZGp1c3RtZW50SgAOc2VxdWVu"
259                 + "Y2VOdW1iZXJJAAh0aHJlYWRJREwABWxldmVsdAAZTGphdmEvdXRpbC9sb2dnaW5n"
260                 + "L0xldmVsO0wACmxvZ2dlck5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMAAdtZXNz"
261                 + "YWdlcQB+AAJMABJyZXNvdXJjZUJ1bmRsZU5hbWVxAH4AAkwAD3NvdXJjZUNsYXNz"
262                 + "TmFtZXEAfgACTAAQc291cmNlTWV0aG9kTmFtZXEAfgACTAAGdGhyb3dudAAVTGph"
263                 + "dmEvbGFuZy9UaHJvd2FibGU7eHAAAAAAgAAACQAAAXLMALDdAAS+2AAAAAAAAAAC"
264                 + "gAAACXNyABdqYXZhLnV0aWwubG9nZ2luZy5MZXZlbI6IcRNRczaSAgADSQAFdmFs"
265                 + "dWVMAARuYW1lcQB+AAJMABJyZXNvdXJjZUJ1bmRsZU5hbWVxAH4AAnhwAAADIHQA"
266                 + "BElORk90ACJzdW4udXRpbC5sb2dnaW5nLnJlc291cmNlcy5sb2dnaW5ndAAFdGVz"
267                 + "dDJ0ABFKYXZhIFZlcnNpb246IHswfXBwcHB3BgEAAAAAAXQACzE2LWludGVybmFs"
268                 + "eA==";
269 
270 
271         @Override
getBase64()272         public String getBase64() {
273             return base64;
274         }
275 
276         @Override
check(LogRecord r1)277         protected void check(LogRecord r1) {
278             super.check(r1);
279         }
280 
test()281         public static void test() {
282             new Jdk16SerializedLog().dotest();
283         }
284     }
285 
286     static enum TestCase { GENERATE, TESTJDK11, TESTJDK16 };
287 
main(String[] args)288     public static void main(String[] args) {
289 
290 
291         // If no args, then run everything....
292         if (args == null || args.length == 0) {
293             args = new String[] { "GENERATE", "TESTJDK11", "TESTJDK16" };
294         }
295 
296         // Run the specified test case(s)
297         Stream.of(args).map(x -> TestCase.valueOf(x)).forEach((x) -> {
298             switch(x) {
299                   case GENERATE:  generate(); break;
300                   case TESTJDK11: Jdk11SerializedLog.test(); break;
301                   case TESTJDK16: Jdk16SerializedLog.test(); break;
302             }
303         });
304     }
305 }
306