From 4409952ec03c5c6d609517205b24eab3474b4e8a Mon Sep 17 00:00:00 2001 From: Zoran Simic Date: Thu, 1 Mar 2012 12:48:51 -0800 Subject: [PATCH 01/12] Replaced org.json (de)serializer with jackson.codehaus.org --- NOTICE.txt | 26 +++---- org.linkedin.util-core/build.gradle | 3 + .../util/io/SortingMapSerializer.java | 74 +++++++++++++++++++ .../util/io/SortingSerializerFactory.java | 52 +++++++++++++ .../linkedin/util/io/ram/RAMDirectory.java | 2 +- org.linkedin.util-groovy/build.gradle | 4 +- .../groovy/util/json/JsonUtils.groovy | 47 ++++++++---- .../test/util/io/TestGroovyIOUtils.groovy | 34 +++++++++ project-spec.groovy | 4 +- 9 files changed, 214 insertions(+), 32 deletions(-) create mode 100644 org.linkedin.util-core/src/main/java/org/linkedin/util/io/SortingMapSerializer.java create mode 100644 org.linkedin.util-core/src/main/java/org/linkedin/util/io/SortingSerializerFactory.java diff --git a/NOTICE.txt b/NOTICE.txt index 16d97d0..1198d7e 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -10,8 +10,8 @@ This product uses the libraries all of which are software developed by The Apache Software Foundation (http://www.apache.org/) ========================================================================= -This product uses a json parser (www.json.org) with the following license (http://www.json.org/license.html) -Copyright (c) 2002 JSON.org +This product uses a json parser (www.json.org) with the following license (http://www.json.org/license.html), Copyright (c) 2002 JSON.org +This product uses the Jackson JSON processor (http://jackson.codehaus.org/) with the Apache License (http://www.apache.org/licenses/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: @@ -63,7 +63,7 @@ This product uses groovy (the language) with the following license: */ ========================================================================= -This product uses gradle (http://www.gradle.org/) for the build framework with the following +This product uses gradle (http://www.gradle.org/) for the build framework with the following license (http://www.gradle.org/license.html): Copyright 2007-2010 the original author or authors @@ -107,7 +107,7 @@ Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are -not derivative works of the Program. +not derivative works of the Program. "Contributor" means any person or entity that distributes the Program. @@ -137,7 +137,7 @@ Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is -licensed hereunder. +licensed hereunder. c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by @@ -153,7 +153,7 @@ acquire that license before distributing the Program. d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license -set forth in this Agreement. +set forth in this Agreement. 3. REQUIREMENTS @@ -167,25 +167,25 @@ its own license agreement, provided that: i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability -and fitness for a particular purpose; +and fitness for a particular purpose; ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential -damages, such as lost profits; +damages, such as lost profits; iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on -or through a medium customarily used for software exchange. +or through a medium customarily used for software exchange. When the Program is made available in source code form: - a) it must be made available under this Agreement; and + a) it must be made available under this Agreement; and b) a copy of this Agreement must be included with each copy of the -Program. +Program. Contributors may not remove or alter any copyright notices contained within the Program. @@ -297,5 +297,5 @@ This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial -in any resulting litigation. - +in any resulting litigation. + diff --git a/org.linkedin.util-core/build.gradle b/org.linkedin.util-core/build.gradle index 1621f90..c6eb3d4 100644 --- a/org.linkedin.util-core/build.gradle +++ b/org.linkedin.util-core/build.gradle @@ -19,6 +19,9 @@ apply plugin: 'org.linkedin.release' dependencies { compile spec.external.slf4j + compile spec.external.jacksoncore + compile spec.external.jacksonmapper + testCompile spec.external.junit testRuntime spec.external.slf4jLog4j } diff --git a/org.linkedin.util-core/src/main/java/org/linkedin/util/io/SortingMapSerializer.java b/org.linkedin.util-core/src/main/java/org/linkedin/util/io/SortingMapSerializer.java new file mode 100644 index 0000000..c390e2a --- /dev/null +++ b/org.linkedin.util-core/src/main/java/org/linkedin/util/io/SortingMapSerializer.java @@ -0,0 +1,74 @@ +package org.linkedin.util.io; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.map.*; +import org.codehaus.jackson.map.ser.std.*; +import org.codehaus.jackson.type.JavaType; + + +/** + * Custom serializer to ensure serialized descendants of Map have their keys alphabetically sorted + */ +public class SortingMapSerializer extends MapSerializer +{ + protected SortingMapSerializer(HashSet ignoredEntries, + JavaType keyType, JavaType valueType, boolean valueTypeIsStatic, + TypeSerializer vts, + JsonSerializer keySerializer, JsonSerializer valueSerializer, + BeanProperty property) + { + super(ignoredEntries, keyType, valueType, valueTypeIsStatic, vts, keySerializer, valueSerializer, property); + } + + + @Override + public void serialize(Map value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonGenerationException + { + if (SortedMap.class.isInstance(value)) { + super.serialize(value, jgen, provider); + } else { + super.serialize(new TreeMap(value), jgen, provider); + } + } + + private static HashSet toSortedSet(String[] ignoredEntries) { + if (ignoredEntries == null || ignoredEntries.length == 0) { + return null; + } + HashSet result = new HashSet(ignoredEntries.length); + for (String prop : ignoredEntries) { + result.add(prop); + } + return result; + } + + public static SortingMapSerializer constructSorted(String[] ignoredList, JavaType mapType, + boolean staticValueType, TypeSerializer vts, BeanProperty property, + JsonSerializer keySerializer, JsonSerializer valueSerializer) + { + HashSet ignoredEntries = toSortedSet(ignoredList); + JavaType keyType, valueType; + + if (mapType == null) { + keyType = valueType = UNSPECIFIED_TYPE; + } else { + keyType = mapType.getKeyType(); + valueType = mapType.getContentType(); + } + // If value type is final, it's same as forcing static value typing: + if (!staticValueType) { + staticValueType = (valueType != null && valueType.isFinal()); + } + return new SortingMapSerializer(ignoredEntries, keyType, valueType, staticValueType, vts, + keySerializer, valueSerializer, property); + } + + +} diff --git a/org.linkedin.util-core/src/main/java/org/linkedin/util/io/SortingSerializerFactory.java b/org.linkedin.util-core/src/main/java/org/linkedin/util/io/SortingSerializerFactory.java new file mode 100644 index 0000000..4006b2e --- /dev/null +++ b/org.linkedin.util-core/src/main/java/org/linkedin/util/io/SortingSerializerFactory.java @@ -0,0 +1,52 @@ +package org.linkedin.util.io; + + +import java.util.EnumMap; +import org.codehaus.jackson.map.*; +import org.codehaus.jackson.map.introspect.BasicBeanDescription; +import org.codehaus.jackson.map.ser.CustomSerializerFactory; +import org.codehaus.jackson.map.ser.std.ToStringSerializer; +import org.codehaus.jackson.map.type.MapType; + + +/** + * Custom serializer factory, to introduce the map-sorting serializer + */ +public class SortingSerializerFactory extends CustomSerializerFactory +{ + protected SortingSerializerFactory() { + super(); + } + /** + * Helper method that handles configuration details when constructing serializers for + * {@link java.util.Map} types. + *

+ * Note: signature changed in 1.8, to take 'staticTyping' argument + */ + protected JsonSerializer buildMapSerializer(SerializationConfig config, MapType type, + BasicBeanDescription beanDesc, BeanProperty property, + boolean staticTyping, + JsonSerializer keySerializer, + TypeSerializer elementTypeSerializer, JsonSerializer elementValueSerializer) + { + for (Serializers serializers : customSerializers()) { + JsonSerializer ser = serializers.findMapSerializer(config, type, beanDesc, property, + keySerializer, elementTypeSerializer, elementValueSerializer); + if (ser != null) { + return ser; + } + } + if (EnumMap.class.isAssignableFrom(type.getRawClass())) { + return buildEnumMapSerializer(config, type, beanDesc, property, staticTyping, + elementTypeSerializer, elementValueSerializer); + } + return SortingMapSerializer.constructSorted(config.getAnnotationIntrospector().findPropertiesToIgnore(beanDesc.getClassInfo()), + type, + staticTyping, + elementTypeSerializer, + property, + keySerializer, + elementValueSerializer); + } + +} diff --git a/org.linkedin.util-core/src/main/java/org/linkedin/util/io/ram/RAMDirectory.java b/org.linkedin.util-core/src/main/java/org/linkedin/util/io/ram/RAMDirectory.java index 3bb449a..18270be 100644 --- a/org.linkedin.util-core/src/main/java/org/linkedin/util/io/ram/RAMDirectory.java +++ b/org.linkedin.util-core/src/main/java/org/linkedin/util/io/ram/RAMDirectory.java @@ -171,7 +171,7 @@ public RAMEntry touch(String name, long lastModifiedDate) * Copy the entry in this directory with the provided name * * @param entry - * @return + * @return the touched entry */ public RAMEntry add(RAMEntry entry) { diff --git a/org.linkedin.util-groovy/build.gradle b/org.linkedin.util-groovy/build.gradle index 23c2a9b..589882f 100644 --- a/org.linkedin.util-groovy/build.gradle +++ b/org.linkedin.util-groovy/build.gradle @@ -24,9 +24,11 @@ dependencies { compile spec.external.slf4jJul compile spec.external.log4j compile spec.external.json + compile spec.external.jacksoncore + compile spec.external.jacksonmapper groovy spec.external.groovy - + testCompile spec.external.junit testRuntime spec.external.slf4jLog4j } diff --git a/org.linkedin.util-groovy/src/main/groovy/org/linkedin/groovy/util/json/JsonUtils.groovy b/org.linkedin.util-groovy/src/main/groovy/org/linkedin/groovy/util/json/JsonUtils.groovy index 56a5f7d..cc12469 100644 --- a/org.linkedin.util-groovy/src/main/groovy/org/linkedin/groovy/util/json/JsonUtils.groovy +++ b/org.linkedin.util-groovy/src/main/groovy/org/linkedin/groovy/util/json/JsonUtils.groovy @@ -15,40 +15,55 @@ */ - package org.linkedin.groovy.util.json +package org.linkedin.groovy.util.json +import org.codehaus.jackson.map.* import org.json.JSONObject import org.json.JSONArray +import org.linkedin.util.io.SortingSerializerFactory +import org.codehaus.jackson.map.ser.std.ToStringSerializer /** * Contains utilities for json. - * + * * @author ypujante@linkedin.com */ class JsonUtils { + private static final jacksonMapper = newSortingMapper() + + static ObjectMapper newSortingMapper() + { + def mapper = new ObjectMapper() + mapper.configure(SerializationConfig.Feature.WRITE_NULL_MAP_VALUES, false) + def sf = new SortingSerializerFactory() + sf.addGenericMapping(GString.class, ToStringSerializer.instance) + mapper.setSerializerFactory(sf) + return mapper + } + /** - * Converts the value to a json object and displays it nicely indented + * Represents the value in JSON, nicely indented and human readable */ - static String prettyPrint(value) + static String prettyPrinted(value) { - prettyPrint(value, 2) + if (value == null) + return null + return jacksonMapper.defaultPrettyPrintingWriter().writeValueAsString(value) } /** - * Converts the value to a json object and displays it nicely indented + * Represents the value in JSON, compact form */ - static String prettyPrint(value, int indent) + static String compactRepresentation(value) { - def json = toJSON(value) - if(json == null) + if (value == null) return null - return json.toString(indent) + return jacksonMapper.writeValueAsString(value) } /** - * Given a json string, convert it to a value (maps / lists): equivalent to - * toValue(new JSONObject(json)) or toValue(new JSONArray(json) + * Given a json string, convert it to a value (map / list) * depending on if the json starts with [ or { * (with proper null handling). */ @@ -58,9 +73,9 @@ class JsonUtils return null json = json.trim() if(json.startsWith('[')) - return toValue(new JSONArray(json)) + return jacksonMapper.readValue(json, ArrayList.class) else - return toValue(new JSONObject(json)) + return jacksonMapper.readValue(json, LinkedHashMap.class) } /** @@ -159,7 +174,7 @@ class JsonUtils list.each { elt -> array.put(toJSON(elt)) } - + return array } -} \ No newline at end of file +} diff --git a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy index abeb5b4..28564e3 100644 --- a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy +++ b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy @@ -23,6 +23,7 @@ import org.linkedin.groovy.util.io.GroovyIOUtils import org.linkedin.groovy.util.net.GroovyNetUtils import com.sun.net.httpserver.HttpExchange import com.sun.net.httpserver.Headers +import org.linkedin.groovy.util.json.JsonUtils /** * @author yan@pongasoft.com */ @@ -40,6 +41,39 @@ public class TestGroovyIOUtils extends GroovyTestCase } } + public void testJsonSortedSerialization() + { + def str1 = 'foo' + def str2 = 'bar' + def map = [b: 'v1', a: "${str1}!=${str2}"] // This map contains a groovy.lang.GString descendant + assertEquals('{"a":"foo!=bar","b":"v1"}', JsonUtils.compactRepresentation(map)) + assertEquals("""{ + "a" : "foo!=bar", + "b" : "v1" +}""", JsonUtils.prettyPrinted(map)) + map = [z: null, a: 'v1', d: ['t2', 't1'], c: 'v3', b: [a: 'b1', c: 'b2', d: ['foo', 'bar'], b: 'b3']]; + assertEquals('{"a":"v1","b":{"a":"b1","b":"b3","c":"b2","d":["foo","bar"]},"c":"v3","d":["t2","t1"]}', JsonUtils.compactRepresentation(map)) + assertEquals("""{ + "a" : "v1", + "b" : { + "a" : "b1", + "b" : "b3", + "c" : "b2", + "d" : [ "foo", "bar" ] + }, + "c" : "v3", + "d" : [ "t2", "t1" ] +}""", JsonUtils.prettyPrinted(map)) + map = [a: 'v1', d: 'v2', c: 'v3', b: 'v4']; + assertEquals('{"a":"v1","b":"v4","c":"v3","d":"v2"}', JsonUtils.compactRepresentation(map)) + assertEquals("""{ + "a" : "v1", + "b" : "v4", + "c" : "v3", + "d" : "v2" +}""", JsonUtils.prettyPrinted(map)) + } + public void testWithFile() { GroovyNetUtils.withHttpEchoServer { int port -> diff --git a/project-spec.groovy b/project-spec.groovy index 22bdfeb..6235a1c 100644 --- a/project-spec.groovy +++ b/project-spec.groovy @@ -18,7 +18,7 @@ spec = [ name: 'linkedin-utils', group: 'org.linkedin', - version: '1.7.2', + version: '1.8.0', versions: [ groovy: '1.7.5', @@ -46,6 +46,8 @@ spec.external = [ ant: 'org.apache.ant:ant:1.8.1', groovy: "org.codehaus.groovy:groovy:${spec.versions.groovy}", json: 'org.json:json:20090211', + jacksoncore: "org.codehaus.jackson:jackson-core-asl:1.9.5", + jacksonmapper: "org.codehaus.jackson:jackson-mapper-asl:1.9.5", junit: 'junit:junit:4.4', log4j: 'log4j:log4j:1.2.16', slf4j: "org.slf4j:slf4j-api:${spec.versions.slf4j}", From 87bee05b8374631255049dae0f405aef4c5ad62c Mon Sep 17 00:00:00 2001 From: Zoran Simic Date: Fri, 2 Mar 2012 09:48:21 -0800 Subject: [PATCH 02/12] Corrected documentation warnings --- .../main/java/org/linkedin/util/codec/Base64Codec.java | 4 ++-- .../org/linkedin/util/concurrent/ExternalCommand.java | 8 ++++---- .../java/org/linkedin/util/io/resource/RAMResource.java | 2 +- .../java/org/linkedin/util/lifecycle/ShutdownProxy.java | 2 +- .../src/main/java/org/linkedin/util/url/URLBuilder.java | 1 - 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/org.linkedin.util-core/src/main/java/org/linkedin/util/codec/Base64Codec.java b/org.linkedin.util-core/src/main/java/org/linkedin/util/codec/Base64Codec.java index 20d4220..228c1d3 100644 --- a/org.linkedin.util-core/src/main/java/org/linkedin/util/codec/Base64Codec.java +++ b/org.linkedin.util-core/src/main/java/org/linkedin/util/codec/Base64Codec.java @@ -301,7 +301,7 @@ public static char[] shuffle(char[] array, Random random) * * @param bits - long value containing the bits to encode * @param numbits - number of bits to encode - * @return + * @return encoded bits */ public String encode(long bits, int numbits) { @@ -318,7 +318,7 @@ public String encode(long bits, int numbits) * Decodes numbits of bits from the string. Invalid or missing digits are treated as 0. * @param s - base64-encoded string to decode * @param numbits - number of bits to extract (LSB first) - * @return + * @return number of bits */ public long decode(String s, int numbits) { diff --git a/org.linkedin.util-core/src/main/java/org/linkedin/util/concurrent/ExternalCommand.java b/org.linkedin.util-core/src/main/java/org/linkedin/util/concurrent/ExternalCommand.java index 65f0a40..03bd4f6 100644 --- a/org.linkedin.util-core/src/main/java/org/linkedin/util/concurrent/ExternalCommand.java +++ b/org.linkedin.util-core/src/main/java/org/linkedin/util/concurrent/ExternalCommand.java @@ -171,7 +171,7 @@ public byte[] getError() throws InterruptedException * Returns the output as a string. * * @param encoding - * @return + * @return output with encoding * @throws InterruptedException * @throws UnsupportedEncodingException */ @@ -184,7 +184,7 @@ public String getStringOutput(String encoding) throws InterruptedException, /** * Returns the output as a string. Uses encoding "UTF-8". * - * @return + * @return output * @throws InterruptedException */ public String getStringOutput() throws InterruptedException @@ -204,7 +204,7 @@ public String getStringOutput() throws InterruptedException * Returns the error as a string. * * @param encoding - * @return + * @return string representing the error with given encoding * @throws InterruptedException * @throws UnsupportedEncodingException */ @@ -217,7 +217,7 @@ public String getStringError(String encoding) throws InterruptedException, /** * Returns the error as a string. Uses encoding "UTF-8". * - * @return + * @return string representing the error * @throws InterruptedException */ public String getStringError() throws InterruptedException diff --git a/org.linkedin.util-core/src/main/java/org/linkedin/util/io/resource/RAMResource.java b/org.linkedin.util-core/src/main/java/org/linkedin/util/io/resource/RAMResource.java index 2eaffb6..34d597b 100644 --- a/org.linkedin.util-core/src/main/java/org/linkedin/util/io/resource/RAMResource.java +++ b/org.linkedin.util-core/src/main/java/org/linkedin/util/io/resource/RAMResource.java @@ -149,7 +149,7 @@ public URI toURI() * Factory of {@link RAMResource} (based on a {@link RAMDirectory} * * @param root - * @return + * @return resource */ public static Resource create(RAMDirectory root) { diff --git a/org.linkedin.util-core/src/main/java/org/linkedin/util/lifecycle/ShutdownProxy.java b/org.linkedin.util-core/src/main/java/org/linkedin/util/lifecycle/ShutdownProxy.java index c1ec0fe..c1129d8 100644 --- a/org.linkedin.util-core/src/main/java/org/linkedin/util/lifecycle/ShutdownProxy.java +++ b/org.linkedin.util-core/src/main/java/org/linkedin/util/lifecycle/ShutdownProxy.java @@ -98,7 +98,7 @@ public Object getProxiedObject() * @param o * @param method * @param objects - * @return + * @return object * @throws Throwable */ @Override diff --git a/org.linkedin.util-core/src/main/java/org/linkedin/util/url/URLBuilder.java b/org.linkedin.util-core/src/main/java/org/linkedin/util/url/URLBuilder.java index 4079574..e093040 100644 --- a/org.linkedin.util-core/src/main/java/org/linkedin/util/url/URLBuilder.java +++ b/org.linkedin.util-core/src/main/java/org/linkedin/util/url/URLBuilder.java @@ -621,7 +621,6 @@ public String[] removeQueryParameter(String name) * return its previous value. * * @param names parameter to remove - * @return previous value or null if parameter doesn't exist */ public void removeQueryParameters(String... names) { From cb0bc4169f35e2917193d36e74a4377711c09189 Mon Sep 17 00:00:00 2001 From: Zoran Simic Date: Thu, 15 Mar 2012 12:54:44 -0700 Subject: [PATCH 03/12] Yan's suggestions taken into account --- org.linkedin.util-core/build.gradle | 2 - .../util/io/SortingMapSerializer.java | 74 ------------ .../groovy/util/json/JsonUtils.groovy | 39 +++++-- .../util/io/SortingMapSerializer.java | 105 ++++++++++++++++++ .../util/io/SortingSerializerFactory.java | 41 +++++-- .../test/util/io/TestGroovyIOUtils.groovy | 17 +-- 6 files changed, 174 insertions(+), 104 deletions(-) delete mode 100644 org.linkedin.util-core/src/main/java/org/linkedin/util/io/SortingMapSerializer.java create mode 100644 org.linkedin.util-groovy/src/main/java/org/linkedin/util/io/SortingMapSerializer.java rename {org.linkedin.util-core => org.linkedin.util-groovy}/src/main/java/org/linkedin/util/io/SortingSerializerFactory.java (61%) diff --git a/org.linkedin.util-core/build.gradle b/org.linkedin.util-core/build.gradle index c6eb3d4..f896811 100644 --- a/org.linkedin.util-core/build.gradle +++ b/org.linkedin.util-core/build.gradle @@ -19,8 +19,6 @@ apply plugin: 'org.linkedin.release' dependencies { compile spec.external.slf4j - compile spec.external.jacksoncore - compile spec.external.jacksonmapper testCompile spec.external.junit testRuntime spec.external.slf4jLog4j diff --git a/org.linkedin.util-core/src/main/java/org/linkedin/util/io/SortingMapSerializer.java b/org.linkedin.util-core/src/main/java/org/linkedin/util/io/SortingMapSerializer.java deleted file mode 100644 index c390e2a..0000000 --- a/org.linkedin.util-core/src/main/java/org/linkedin/util/io/SortingMapSerializer.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.linkedin.util.io; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; -import org.codehaus.jackson.JsonGenerationException; -import org.codehaus.jackson.JsonGenerator; -import org.codehaus.jackson.map.*; -import org.codehaus.jackson.map.ser.std.*; -import org.codehaus.jackson.type.JavaType; - - -/** - * Custom serializer to ensure serialized descendants of Map have their keys alphabetically sorted - */ -public class SortingMapSerializer extends MapSerializer -{ - protected SortingMapSerializer(HashSet ignoredEntries, - JavaType keyType, JavaType valueType, boolean valueTypeIsStatic, - TypeSerializer vts, - JsonSerializer keySerializer, JsonSerializer valueSerializer, - BeanProperty property) - { - super(ignoredEntries, keyType, valueType, valueTypeIsStatic, vts, keySerializer, valueSerializer, property); - } - - - @Override - public void serialize(Map value, JsonGenerator jgen, SerializerProvider provider) - throws IOException, JsonGenerationException - { - if (SortedMap.class.isInstance(value)) { - super.serialize(value, jgen, provider); - } else { - super.serialize(new TreeMap(value), jgen, provider); - } - } - - private static HashSet toSortedSet(String[] ignoredEntries) { - if (ignoredEntries == null || ignoredEntries.length == 0) { - return null; - } - HashSet result = new HashSet(ignoredEntries.length); - for (String prop : ignoredEntries) { - result.add(prop); - } - return result; - } - - public static SortingMapSerializer constructSorted(String[] ignoredList, JavaType mapType, - boolean staticValueType, TypeSerializer vts, BeanProperty property, - JsonSerializer keySerializer, JsonSerializer valueSerializer) - { - HashSet ignoredEntries = toSortedSet(ignoredList); - JavaType keyType, valueType; - - if (mapType == null) { - keyType = valueType = UNSPECIFIED_TYPE; - } else { - keyType = mapType.getKeyType(); - valueType = mapType.getContentType(); - } - // If value type is final, it's same as forcing static value typing: - if (!staticValueType) { - staticValueType = (valueType != null && valueType.isFinal()); - } - return new SortingMapSerializer(ignoredEntries, keyType, valueType, staticValueType, vts, - keySerializer, valueSerializer, property); - } - - -} diff --git a/org.linkedin.util-groovy/src/main/groovy/org/linkedin/groovy/util/json/JsonUtils.groovy b/org.linkedin.util-groovy/src/main/groovy/org/linkedin/groovy/util/json/JsonUtils.groovy index cc12469..3040c82 100644 --- a/org.linkedin.util-groovy/src/main/groovy/org/linkedin/groovy/util/json/JsonUtils.groovy +++ b/org.linkedin.util-groovy/src/main/groovy/org/linkedin/groovy/util/json/JsonUtils.groovy @@ -17,10 +17,12 @@ package org.linkedin.groovy.util.json -import org.codehaus.jackson.map.* import org.json.JSONObject import org.json.JSONArray import org.linkedin.util.io.SortingSerializerFactory +import org.codehaus.jackson.map.ObjectMapper +import org.codehaus.jackson.map.SerializationConfig +import org.codehaus.jackson.map.ser.CustomSerializerFactory import org.codehaus.jackson.map.ser.std.ToStringSerializer /** @@ -30,36 +32,49 @@ import org.codehaus.jackson.map.ser.std.ToStringSerializer */ class JsonUtils { - private static final jacksonMapper = newSortingMapper() + private static final JACKSON_MAPPER = newJacksonMapper(false) + private static final JACKSON_SORTING_MAPPER = newJacksonMapper(true) - static ObjectMapper newSortingMapper() + static ObjectMapper newJacksonMapper(sorting) { def mapper = new ObjectMapper() mapper.configure(SerializationConfig.Feature.WRITE_NULL_MAP_VALUES, false) - def sf = new SortingSerializerFactory() + def sf = sorting ? new SortingSerializerFactory() : new CustomSerializerFactory() sf.addGenericMapping(GString.class, ToStringSerializer.instance) mapper.setSerializerFactory(sf) return mapper } - + + /** + * Represents the value in JSON, nicely indented and human readable depending on 'indent' + */ + static String prettyPrint(value, int indent) + { + if(indent) + return prettyPrint(value) + else + return compactPrint(value) + } + + /** * Represents the value in JSON, nicely indented and human readable */ - static String prettyPrinted(value) + static String prettyPrint(value) { if (value == null) return null - return jacksonMapper.defaultPrettyPrintingWriter().writeValueAsString(value) + return JACKSON_SORTING_MAPPER.defaultPrettyPrintingWriter().writeValueAsString(value) } /** - * Represents the value in JSON, compact form + * Represents the value in JSON, compact form (keys are not sorted) */ - static String compactRepresentation(value) + static String compactPrint(value) { if (value == null) return null - return jacksonMapper.writeValueAsString(value) + return JACKSON_MAPPER.writeValueAsString(value) } /** @@ -73,9 +88,9 @@ class JsonUtils return null json = json.trim() if(json.startsWith('[')) - return jacksonMapper.readValue(json, ArrayList.class) + return JACKSON_SORTING_MAPPER.readValue(json, ArrayList.class) else - return jacksonMapper.readValue(json, LinkedHashMap.class) + return JACKSON_SORTING_MAPPER.readValue(json, LinkedHashMap.class) } /** diff --git a/org.linkedin.util-groovy/src/main/java/org/linkedin/util/io/SortingMapSerializer.java b/org.linkedin.util-groovy/src/main/java/org/linkedin/util/io/SortingMapSerializer.java new file mode 100644 index 0000000..e393480 --- /dev/null +++ b/org.linkedin.util-groovy/src/main/java/org/linkedin/util/io/SortingMapSerializer.java @@ -0,0 +1,105 @@ +/** + * This is a temporary solution to allow having map keys sorted when serializing with Jackson + * This class should be removed (and JsonUtils adapted accordingly) as soon as Jackson 2.0 is released + * This code is a very small adaptation of MapSerializer + * + * Copyright 2012 LinkedIn, Inc + * + * Licensed 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.linkedin.util.io; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.map.BeanProperty; +import org.codehaus.jackson.map.JsonSerializer; +import org.codehaus.jackson.map.SerializerProvider; +import org.codehaus.jackson.map.TypeSerializer; +import org.codehaus.jackson.map.ser.std.MapSerializer; +import org.codehaus.jackson.type.JavaType; + +/** + * Custom serializer to ensure serialized descendants of Map have their keys alphabetically sorted + */ + public class SortingMapSerializer extends MapSerializer + { + protected SortingMapSerializer(HashSet ignoredEntries, + JavaType keyType, JavaType valueType, boolean valueTypeIsStatic, + TypeSerializer vts, + JsonSerializer keySerializer, JsonSerializer valueSerializer, + BeanProperty property) + { + super(ignoredEntries, keyType, valueType, valueTypeIsStatic, vts, keySerializer, valueSerializer, property); + } + + + @Override + public void serialize(Map value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonGenerationException + { + if (SortedMap.class.isInstance(value)) + { + super.serialize(value, jgen, provider); + } + else + { + super.serialize(new TreeMap(value), jgen, provider); + } + } + + private static HashSet toSortedSet(String[] ignoredEntries) { + if (ignoredEntries == null || ignoredEntries.length == 0) + { + return null; + } + HashSet result = new HashSet(ignoredEntries.length); + for (String prop : ignoredEntries) + { + result.add(prop); + } + return result; + } + + public static SortingMapSerializer constructSorted(String[] ignoredList, JavaType mapType, + boolean staticValueType, TypeSerializer vts, BeanProperty property, + JsonSerializer keySerializer, JsonSerializer valueSerializer) + { + HashSet ignoredEntries = toSortedSet(ignoredList); + JavaType keyType, valueType; + + if (mapType == null) + { + keyType = valueType = UNSPECIFIED_TYPE; + } + else + { + keyType = mapType.getKeyType(); + valueType = mapType.getContentType(); + } + // If value type is final, it's same as forcing static value typing: + if (!staticValueType) + { + staticValueType = (valueType != null && valueType.isFinal()); + } + return new SortingMapSerializer(ignoredEntries, keyType, valueType, staticValueType, vts, + keySerializer, valueSerializer, property); + } + + +} diff --git a/org.linkedin.util-core/src/main/java/org/linkedin/util/io/SortingSerializerFactory.java b/org.linkedin.util-groovy/src/main/java/org/linkedin/util/io/SortingSerializerFactory.java similarity index 61% rename from org.linkedin.util-core/src/main/java/org/linkedin/util/io/SortingSerializerFactory.java rename to org.linkedin.util-groovy/src/main/java/org/linkedin/util/io/SortingSerializerFactory.java index 4006b2e..e376f00 100644 --- a/org.linkedin.util-core/src/main/java/org/linkedin/util/io/SortingSerializerFactory.java +++ b/org.linkedin.util-groovy/src/main/java/org/linkedin/util/io/SortingSerializerFactory.java @@ -1,20 +1,42 @@ -package org.linkedin.util.io; +/** + * This is a temporary solution to allow having map keys sorted when serializing with Jackson + * This class should be removed (and JsonUtils adapted accordingly) as soon as Jackson 2.0 is released + * This code is a very small adaptation of CustomSerializerFactory + * + * Copyright 2012 LinkedIn, Inc + * + * Licensed 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.linkedin.util.io; import java.util.EnumMap; -import org.codehaus.jackson.map.*; +import org.codehaus.jackson.map.BeanProperty; +import org.codehaus.jackson.map.JsonSerializer; +import org.codehaus.jackson.map.SerializationConfig; +import org.codehaus.jackson.map.Serializers; +import org.codehaus.jackson.map.TypeSerializer; import org.codehaus.jackson.map.introspect.BasicBeanDescription; import org.codehaus.jackson.map.ser.CustomSerializerFactory; -import org.codehaus.jackson.map.ser.std.ToStringSerializer; import org.codehaus.jackson.map.type.MapType; - /** * Custom serializer factory, to introduce the map-sorting serializer */ public class SortingSerializerFactory extends CustomSerializerFactory { - protected SortingSerializerFactory() { + protected SortingSerializerFactory() + { super(); } /** @@ -29,14 +51,17 @@ protected JsonSerializer buildMapSerializer(SerializationConfig config, MapTy JsonSerializer keySerializer, TypeSerializer elementTypeSerializer, JsonSerializer elementValueSerializer) { - for (Serializers serializers : customSerializers()) { + for (Serializers serializers : customSerializers()) + { JsonSerializer ser = serializers.findMapSerializer(config, type, beanDesc, property, keySerializer, elementTypeSerializer, elementValueSerializer); - if (ser != null) { + if (ser != null) + { return ser; } } - if (EnumMap.class.isAssignableFrom(type.getRawClass())) { + if (EnumMap.class.isAssignableFrom(type.getRawClass())) + { return buildEnumMapSerializer(config, type, beanDesc, property, staticTyping, elementTypeSerializer, elementValueSerializer); } diff --git a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy index 28564e3..c6e706e 100644 --- a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy +++ b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy @@ -46,13 +46,15 @@ public class TestGroovyIOUtils extends GroovyTestCase def str1 = 'foo' def str2 = 'bar' def map = [b: 'v1', a: "${str1}!=${str2}"] // This map contains a groovy.lang.GString descendant - assertEquals('{"a":"foo!=bar","b":"v1"}', JsonUtils.compactRepresentation(map)) + def deserialized_map = JsonUtils.fromJSON(JsonUtils.compactPrint(map)) + assertEquals(map['b'], deserialized_map['b']) + assertEquals(map['a'], deserialized_map['a']) + assertEquals(map.size(), deserialized_map.size()) assertEquals("""{ "a" : "foo!=bar", "b" : "v1" -}""", JsonUtils.prettyPrinted(map)) +}""", JsonUtils.prettyPrint(map)) map = [z: null, a: 'v1', d: ['t2', 't1'], c: 'v3', b: [a: 'b1', c: 'b2', d: ['foo', 'bar'], b: 'b3']]; - assertEquals('{"a":"v1","b":{"a":"b1","b":"b3","c":"b2","d":["foo","bar"]},"c":"v3","d":["t2","t1"]}', JsonUtils.compactRepresentation(map)) assertEquals("""{ "a" : "v1", "b" : { @@ -63,17 +65,16 @@ public class TestGroovyIOUtils extends GroovyTestCase }, "c" : "v3", "d" : [ "t2", "t1" ] -}""", JsonUtils.prettyPrinted(map)) +}""", JsonUtils.prettyPrint(map)) map = [a: 'v1', d: 'v2', c: 'v3', b: 'v4']; - assertEquals('{"a":"v1","b":"v4","c":"v3","d":"v2"}', JsonUtils.compactRepresentation(map)) assertEquals("""{ "a" : "v1", "b" : "v4", "c" : "v3", "d" : "v2" -}""", JsonUtils.prettyPrinted(map)) +}""", JsonUtils.prettyPrint(map)) } - + public void testWithFile() { GroovyNetUtils.withHttpEchoServer { int port -> @@ -235,4 +236,4 @@ public class TestGroovyIOUtils extends GroovyTestCase } } } -} \ No newline at end of file +} From 16234ccfdf0462d978c24413c79db434e65bb501 Mon Sep 17 00:00:00 2001 From: Zoran Simic Date: Wed, 28 Mar 2012 16:45:37 -0700 Subject: [PATCH 04/12] Added explanation as to why we need a test case involving a GString when deserializing with Jackson --- .../src/test/groovy/test/util/io/TestGroovyIOUtils.groovy | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy index c6e706e..14edac9 100644 --- a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy +++ b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy @@ -46,6 +46,12 @@ public class TestGroovyIOUtils extends GroovyTestCase def str1 = 'foo' def str2 = 'bar' def map = [b: 'v1', a: "${str1}!=${str2}"] // This map contains a groovy.lang.GString descendant + // Descendants of groovy.lang.GString (example: org.codehaus.groovy.runtime.GstringImpl) + // must be deserialized by Jackson using toString() instead of Java reflexion. + // Otherwise 'statusInfo' will be deserialized in a weird way, something like: + // "statusInfo": { "values": ["running", "stopped"], "strings": ["", "!=", ""], "valueCount": 2 } + // instead of: + // "statusInfo": "running!=stopped" def deserialized_map = JsonUtils.fromJSON(JsonUtils.compactPrint(map)) assertEquals(map['b'], deserialized_map['b']) assertEquals(map['a'], deserialized_map['a']) From 97928e83acd1658418b5c0553b84ea189c076e19 Mon Sep 17 00:00:00 2001 From: Zoran Simic Date: Mon, 2 Apr 2012 19:10:12 -0700 Subject: [PATCH 05/12] Removed unnecessary files related to Jackson 1.9.5 --- .../util/io/SortingMapSerializer.java | 105 -------- .../util/io/SortingSerializerFactory.java | 77 ------ .../test/util/io/TestGroovyIOUtils.groovy | 245 ------------------ 3 files changed, 427 deletions(-) delete mode 100644 org.linkedin.util-groovy/src/main/java/org/linkedin/util/io/SortingMapSerializer.java delete mode 100644 org.linkedin.util-groovy/src/main/java/org/linkedin/util/io/SortingSerializerFactory.java delete mode 100644 org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy diff --git a/org.linkedin.util-groovy/src/main/java/org/linkedin/util/io/SortingMapSerializer.java b/org.linkedin.util-groovy/src/main/java/org/linkedin/util/io/SortingMapSerializer.java deleted file mode 100644 index e393480..0000000 --- a/org.linkedin.util-groovy/src/main/java/org/linkedin/util/io/SortingMapSerializer.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * This is a temporary solution to allow having map keys sorted when serializing with Jackson - * This class should be removed (and JsonUtils adapted accordingly) as soon as Jackson 2.0 is released - * This code is a very small adaptation of MapSerializer - * - * Copyright 2012 LinkedIn, Inc - * - * Licensed 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.linkedin.util.io; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; -import org.codehaus.jackson.JsonGenerationException; -import org.codehaus.jackson.JsonGenerator; -import org.codehaus.jackson.map.BeanProperty; -import org.codehaus.jackson.map.JsonSerializer; -import org.codehaus.jackson.map.SerializerProvider; -import org.codehaus.jackson.map.TypeSerializer; -import org.codehaus.jackson.map.ser.std.MapSerializer; -import org.codehaus.jackson.type.JavaType; - -/** - * Custom serializer to ensure serialized descendants of Map have their keys alphabetically sorted - */ - public class SortingMapSerializer extends MapSerializer - { - protected SortingMapSerializer(HashSet ignoredEntries, - JavaType keyType, JavaType valueType, boolean valueTypeIsStatic, - TypeSerializer vts, - JsonSerializer keySerializer, JsonSerializer valueSerializer, - BeanProperty property) - { - super(ignoredEntries, keyType, valueType, valueTypeIsStatic, vts, keySerializer, valueSerializer, property); - } - - - @Override - public void serialize(Map value, JsonGenerator jgen, SerializerProvider provider) - throws IOException, JsonGenerationException - { - if (SortedMap.class.isInstance(value)) - { - super.serialize(value, jgen, provider); - } - else - { - super.serialize(new TreeMap(value), jgen, provider); - } - } - - private static HashSet toSortedSet(String[] ignoredEntries) { - if (ignoredEntries == null || ignoredEntries.length == 0) - { - return null; - } - HashSet result = new HashSet(ignoredEntries.length); - for (String prop : ignoredEntries) - { - result.add(prop); - } - return result; - } - - public static SortingMapSerializer constructSorted(String[] ignoredList, JavaType mapType, - boolean staticValueType, TypeSerializer vts, BeanProperty property, - JsonSerializer keySerializer, JsonSerializer valueSerializer) - { - HashSet ignoredEntries = toSortedSet(ignoredList); - JavaType keyType, valueType; - - if (mapType == null) - { - keyType = valueType = UNSPECIFIED_TYPE; - } - else - { - keyType = mapType.getKeyType(); - valueType = mapType.getContentType(); - } - // If value type is final, it's same as forcing static value typing: - if (!staticValueType) - { - staticValueType = (valueType != null && valueType.isFinal()); - } - return new SortingMapSerializer(ignoredEntries, keyType, valueType, staticValueType, vts, - keySerializer, valueSerializer, property); - } - - -} diff --git a/org.linkedin.util-groovy/src/main/java/org/linkedin/util/io/SortingSerializerFactory.java b/org.linkedin.util-groovy/src/main/java/org/linkedin/util/io/SortingSerializerFactory.java deleted file mode 100644 index e376f00..0000000 --- a/org.linkedin.util-groovy/src/main/java/org/linkedin/util/io/SortingSerializerFactory.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * This is a temporary solution to allow having map keys sorted when serializing with Jackson - * This class should be removed (and JsonUtils adapted accordingly) as soon as Jackson 2.0 is released - * This code is a very small adaptation of CustomSerializerFactory - * - * Copyright 2012 LinkedIn, Inc - * - * Licensed 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.linkedin.util.io; - -import java.util.EnumMap; -import org.codehaus.jackson.map.BeanProperty; -import org.codehaus.jackson.map.JsonSerializer; -import org.codehaus.jackson.map.SerializationConfig; -import org.codehaus.jackson.map.Serializers; -import org.codehaus.jackson.map.TypeSerializer; -import org.codehaus.jackson.map.introspect.BasicBeanDescription; -import org.codehaus.jackson.map.ser.CustomSerializerFactory; -import org.codehaus.jackson.map.type.MapType; - -/** - * Custom serializer factory, to introduce the map-sorting serializer - */ -public class SortingSerializerFactory extends CustomSerializerFactory -{ - protected SortingSerializerFactory() - { - super(); - } - /** - * Helper method that handles configuration details when constructing serializers for - * {@link java.util.Map} types. - *

- * Note: signature changed in 1.8, to take 'staticTyping' argument - */ - protected JsonSerializer buildMapSerializer(SerializationConfig config, MapType type, - BasicBeanDescription beanDesc, BeanProperty property, - boolean staticTyping, - JsonSerializer keySerializer, - TypeSerializer elementTypeSerializer, JsonSerializer elementValueSerializer) - { - for (Serializers serializers : customSerializers()) - { - JsonSerializer ser = serializers.findMapSerializer(config, type, beanDesc, property, - keySerializer, elementTypeSerializer, elementValueSerializer); - if (ser != null) - { - return ser; - } - } - if (EnumMap.class.isAssignableFrom(type.getRawClass())) - { - return buildEnumMapSerializer(config, type, beanDesc, property, staticTyping, - elementTypeSerializer, elementValueSerializer); - } - return SortingMapSerializer.constructSorted(config.getAnnotationIntrospector().findPropertiesToIgnore(beanDesc.getClassInfo()), - type, - staticTyping, - elementTypeSerializer, - property, - keySerializer, - elementValueSerializer); - } - -} diff --git a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy deleted file mode 100644 index 14edac9..0000000 --- a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (c) 2011 Yan Pujante - * - * Licensed 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 test.util.io - -import org.linkedin.groovy.util.io.fs.FileSystemImpl -import org.linkedin.groovy.util.io.fs.FileSystem -import org.linkedin.util.io.resource.Resource -import org.linkedin.groovy.util.io.GroovyIOUtils - -import org.linkedin.groovy.util.net.GroovyNetUtils -import com.sun.net.httpserver.HttpExchange -import com.sun.net.httpserver.Headers -import org.linkedin.groovy.util.json.JsonUtils - -/** - * @author yan@pongasoft.com */ -public class TestGroovyIOUtils extends GroovyTestCase -{ - public void testCat() - { - FileSystemImpl.createTempFileSystem { FileSystem fs -> - - Resource r1 = fs.saveContent('/dir/file1', 'abcd') - - assertEquals('abcd', GroovyIOUtils.cat(r1.toURI())) - assertEquals('abcd', GroovyIOUtils.cat(r1)) - assertEquals('abcd', GroovyIOUtils.cat(r1.file.canonicalPath)) - } - } - - public void testJsonSortedSerialization() - { - def str1 = 'foo' - def str2 = 'bar' - def map = [b: 'v1', a: "${str1}!=${str2}"] // This map contains a groovy.lang.GString descendant - // Descendants of groovy.lang.GString (example: org.codehaus.groovy.runtime.GstringImpl) - // must be deserialized by Jackson using toString() instead of Java reflexion. - // Otherwise 'statusInfo' will be deserialized in a weird way, something like: - // "statusInfo": { "values": ["running", "stopped"], "strings": ["", "!=", ""], "valueCount": 2 } - // instead of: - // "statusInfo": "running!=stopped" - def deserialized_map = JsonUtils.fromJSON(JsonUtils.compactPrint(map)) - assertEquals(map['b'], deserialized_map['b']) - assertEquals(map['a'], deserialized_map['a']) - assertEquals(map.size(), deserialized_map.size()) - assertEquals("""{ - "a" : "foo!=bar", - "b" : "v1" -}""", JsonUtils.prettyPrint(map)) - map = [z: null, a: 'v1', d: ['t2', 't1'], c: 'v3', b: [a: 'b1', c: 'b2', d: ['foo', 'bar'], b: 'b3']]; - assertEquals("""{ - "a" : "v1", - "b" : { - "a" : "b1", - "b" : "b3", - "c" : "b2", - "d" : [ "foo", "bar" ] - }, - "c" : "v3", - "d" : [ "t2", "t1" ] -}""", JsonUtils.prettyPrint(map)) - map = [a: 'v1', d: 'v2', c: 'v3', b: 'v4']; - assertEquals("""{ - "a" : "v1", - "b" : "v4", - "c" : "v3", - "d" : "v2" -}""", JsonUtils.prettyPrint(map)) - } - - public void testWithFile() - { - GroovyNetUtils.withHttpEchoServer { int port -> - FileSystemImpl.createTempFileSystem { FileSystem fs -> - assertEquals("abcd", GroovyIOUtils.cat(new URI("http://localhost:${port}/echo?msg=abcd"))) - Resource r1 = fs.saveContent('/dir/file1', 'abcd') - - GroovyIOUtils.withFile(r1) { File f -> - assertEquals(r1.file, f) - assertEquals("abcd", f.text) - } - - GroovyIOUtils.withFile(r1.toURI()) { File f -> - assertEquals(r1.file, f) - assertEquals("abcd", f.text) - } - - GroovyIOUtils.withFile(r1.file) { File f -> - assertEquals(r1.file, f) - assertEquals("abcd", f.text) - } - - GroovyIOUtils.withFile(r1.file.canonicalPath) { File f -> - assertEquals(r1.file, f) - assertEquals("abcd", f.text) - } - - GroovyIOUtils.withFile(null) { File f -> - assertNull(f) - } - - // this pattern is exactly the kind of pattern that should *not* be followed! - // here I am testing that once the closure is over the file is properly deleted - File tempFile = null - - GroovyIOUtils.withFile("http://localhost:${port}/echo?msg=abcd") { File f -> - tempFile = f - assertTrue(tempFile.exists()) - assertEquals("abcd", f.text) - } - - assertFalse(tempFile.exists()) - - // r1 should still exists - assertTrue(r1.exists()) - } - } - } - - /** - * Verifys that testSafeOverwrite properly works - */ - public void testSafeOverwrite() - { - FileSystemImpl.createTempFileSystem { FileSystem fs -> - File root = fs.root.file - - File tmpFile = null - GroovyIOUtils.safeOverwrite(new File(root, "foo.txt")) { File f -> - tmpFile = f - - // should be a non existent file - assertFalse(f.exists()) - - // should be in the same folder as 'foo.txt' - assertEquals(root, f.parentFile) - assertTrue(f.name.startsWith('++tmp.')) - assertTrue(f.name.endsWith('.tmp++')) - - f.text = "abcd" - } - // after the call the file should be deleted - assertFalse(tmpFile.exists()) - - // the file foo.txt should have been created - assertEquals("abcd", new File(root, "foo.txt").text) - - // error cases - File foo2 = new File(root, "foo2.txt") - foo2.text = 'defg' - - assertEquals("forcing: klmn", shouldFail(Exception.class) { - GroovyIOUtils.safeOverwrite(foo2) { File f -> - tmpFile = f - f.text = 'klmn' - throw new Exception('forcing: klmn') - } - }) - - // after the call the file should be deleted - assertFalse(tmpFile.exists()) - - // the original file should be the same - assertEquals('defg', foo2.text) - - // if file is never written, it means the original should not exist - GroovyIOUtils.safeOverwrite(foo2) { File f -> - tmpFile = f - // don't do anything with f - } - - // after the call the file should be deleted - assertFalse(tmpFile.exists()) - - // the original file should be the same - assertEquals('defg', foo2.text) - - // now test for factory - GroovyIOUtils.safeOverwrite(foo2, {File f -> new File(f.parentFile, "lolo")}) { File f -> - tmpFile = f - assertEquals(new File(root, 'lolo'), f) - f.text = 'hijk' - } - - // after the call the file should be deleted - assertFalse(tmpFile.exists()) - - // the original file should have been updated - assertEquals('hijk', foo2.text) - } - } - - /** - * Test for fetch content. Make sure that authorization works properly - */ - public void testFetchContent() - { - String response - Headers requestHeaders - - def handler = { HttpExchange t -> - requestHeaders = t.requestHeaders - t.sendResponseHeaders(200, response.length()); - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); - } - - GroovyNetUtils.withHttpServer(0, ['/content': handler]) { int port -> - - FileSystemImpl.createTempFileSystem { FileSystem fs -> - File root = fs.root.file - - File tmpFile = new File(root, 'foo.txt') - - response = "abc" - - GroovyIOUtils.fetchContent("http://localhost:${port}/content", tmpFile) - assertEquals("abc", tmpFile.text) - // no authorization header should be present! - assertFalse(requestHeaders.containsKey('Authorization')) - - response = "def" - GroovyIOUtils.fetchContent("http://u1:p1@localhost:${port}/content", tmpFile) - assertEquals("def", tmpFile.text) - // authorization header should be present and properly base64ified - assertEquals("Basic ${'u1:p1'.bytes.encodeBase64()}", - requestHeaders['Authorization'].iterator().next()) - } - } - } -} From 47065924728d9e8846540c07a48beccb95b936bb Mon Sep 17 00:00:00 2001 From: haiping han Date: Mon, 18 Jun 2012 17:29:26 -0700 Subject: [PATCH 06/12] Improving DB password masking --- .../util/io/TestDataMaskingInputStream.groovy | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy diff --git a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy new file mode 100644 index 0000000..12ea42e --- /dev/null +++ b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy @@ -0,0 +1,29 @@ +package test.util.io + +import org.linkedin.groovy.util.io.DataMaskingInputStream; +/** + * Created by IntelliJ IDEA. + * User: hhan + * Date: 6/18/12 + * Time: 3:16 PM + */ +class TestDataMaskingInputStream extends GroovyTestCase { + + + void testOraleDBContent() { + def temp = File.createTempFile("cfg2", "properties") + temp.write ' \n' + def line = new DataMaskingInputStream(temp.newDataInputStream()).readLines()[0] + assertEquals(line, '') + temp.deleteOnExit(); + } + + void testMySQLDBContent() { + def temp = File.createTempFile("cfg2", "properties") + temp.write ' \n' + + def line = new DataMaskingInputStream(temp.newDataInputStream()).readlines[0] + assertEquals(line, '') + temp.deleteOnExit(); + } +} From a17bbc607d3b1423e94e4df771a05f859e60d900 Mon Sep 17 00:00:00 2001 From: haiping han Date: Mon, 18 Jun 2012 17:32:13 -0700 Subject: [PATCH 07/12] Improving DB password masking --- .../linkedin/groovy/util/io/DataMaskingInputStream.groovy | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/org.linkedin.util-groovy/src/main/groovy/org/linkedin/groovy/util/io/DataMaskingInputStream.groovy b/org.linkedin.util-groovy/src/main/groovy/org/linkedin/groovy/util/io/DataMaskingInputStream.groovy index ed9f29e..0c430a1 100644 --- a/org.linkedin.util-groovy/src/main/groovy/org/linkedin/groovy/util/io/DataMaskingInputStream.groovy +++ b/org.linkedin.util-groovy/src/main/groovy/org/linkedin/groovy/util/io/DataMaskingInputStream.groovy @@ -113,8 +113,12 @@ public class DataMaskingInputStream extends FilterInputStream { value = "********" } + if(value.contains('password=')){ + value=value.replaceAll("password=[^&]*", "password=********") + } + if (value.contains('oracle')) { - value = value.replaceAll("\\w*/\\w*", '********/********') + value = value.replaceAll("\\w*/[^@]*", '********/********') } return "${prefix}${key}${middle}${value}${suffix}" From fee378d28f8689c74ae7e63e6e0a0e6d04f4b382 Mon Sep 17 00:00:00 2001 From: Zoran Simic Date: Tue, 19 Jun 2012 16:06:22 -0700 Subject: [PATCH 08/12] Bumped up version to 1.8.1 --- .../test/groovy/test/util/io/TestDataMaskingInputStream.groovy | 2 +- project-spec.groovy | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy index 12ea42e..3266f99 100644 --- a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy +++ b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy @@ -10,7 +10,7 @@ import org.linkedin.groovy.util.io.DataMaskingInputStream; class TestDataMaskingInputStream extends GroovyTestCase { - void testOraleDBContent() { + void testOracleDBContent() { def temp = File.createTempFile("cfg2", "properties") temp.write ' \n' def line = new DataMaskingInputStream(temp.newDataInputStream()).readLines()[0] diff --git a/project-spec.groovy b/project-spec.groovy index 7ea1771..ef0dc5f 100644 --- a/project-spec.groovy +++ b/project-spec.groovy @@ -18,7 +18,7 @@ spec = [ name: 'linkedin-utils', group: 'org.linkedin', - version: '1.8.0', + version: '1.8.1', versions: [ groovy: '1.7.5', From f405f7a262f85b09cbd566b528ebdc0ecdd3b6d0 Mon Sep 17 00:00:00 2001 From: haiping han Date: Tue, 26 Jun 2012 16:58:27 -0700 Subject: [PATCH 09/12] fix test --- .../test/util/io/TestDataMaskingInputStream.groovy | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy index 12ea42e..d1d76b9 100644 --- a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy +++ b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy @@ -1,6 +1,7 @@ package test.util.io -import org.linkedin.groovy.util.io.DataMaskingInputStream; +import org.linkedin.groovy.util.io.DataMaskingInputStream + /** * Created by IntelliJ IDEA. * User: hhan @@ -13,8 +14,9 @@ class TestDataMaskingInputStream extends GroovyTestCase { void testOraleDBContent() { def temp = File.createTempFile("cfg2", "properties") temp.write ' \n' - def line = new DataMaskingInputStream(temp.newDataInputStream()).readLines()[0] - assertEquals(line, '') + String line = new DataMaskingInputStream(temp.newDataInputStream()).readLines()[0] + String expected = '' + assertFalse (line.contains("AES/CBC/PKCS5Padding(3QIdAjOKfAqcetGKhHEWez")) temp.deleteOnExit(); } @@ -22,8 +24,9 @@ class TestDataMaskingInputStream extends GroovyTestCase { def temp = File.createTempFile("cfg2", "properties") temp.write ' \n' - def line = new DataMaskingInputStream(temp.newDataInputStream()).readlines[0] - assertEquals(line, '') + String line = new DataMaskingInputStream(temp.newDataInputStream()).readLines()[0] + String expected = '' + assertFalse(line.contains("password=test")) temp.deleteOnExit(); } } From 30108fc09e5b61d1307b919194c58f2404212bbf Mon Sep 17 00:00:00 2001 From: Zoran Simic Date: Mon, 2 Jul 2012 13:23:46 -0700 Subject: [PATCH 10/12] Restored TestGroovyIOUtils.groovy, reviewed TestDataMaskingInputStream.groovy TestGroovyIOUtils.groovy was apparently mistakenly removed in last merge. --- RELEASE.md | 8 +- .../util/io/TestDataMaskingInputStream.groovy | 67 ++++-- .../test/util/io/TestGroovyIOUtils.groovy | 204 ++++++++++++++++++ 3 files changed, 258 insertions(+), 21 deletions(-) create mode 100644 org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy diff --git a/RELEASE.md b/RELEASE.md index d2e9783..a6fe356 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,3 +1,7 @@ +1.8.1 (2012/06/26) +------------------ +* improved DB password masking to mask special characters (non alpha-numeric) as well + 1.8.0 (2012/03/31) ------------------ * implemented [ticket #6](https://github.com/linkedin/linkedin-utils/issues/6): _Using Jackson JSON (de)serializer_ (thanks for the help from Zoran @ LinkedIn) @@ -41,7 +45,7 @@ Note that ``prettyPrint`` returns a slightly different output than before (keys ------------------ * fixed [bug #1](https://github.com/linkedin/linkedin-utils/issues/1): _GroovyIOUtils.cat leaks memory_ - revisited several concepts dealing with the creation of temporary files + revisited several concepts dealing with the creation of temporary files 1.3.0 (2011/01/17) ------------------ @@ -59,4 +63,4 @@ Note that ``prettyPrint`` returns a slightly different output than before (keys 1.0.0 (2010/11/05) ------------------ -* First release \ No newline at end of file +* First release diff --git a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy index 99878c2..d804d9a 100644 --- a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy +++ b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy @@ -1,32 +1,61 @@ +/* + * Copyright 2010-2010 LinkedIn, Inc + * + * Licensed 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 test.util.io import org.linkedin.groovy.util.io.DataMaskingInputStream /** - * Created by IntelliJ IDEA. * User: hhan * Date: 6/18/12 * Time: 3:16 PM + * @author hhan@linkedin.com */ class TestDataMaskingInputStream extends GroovyTestCase { + void testOracleDBContent() + { + def temp = File.createTempFile("cfg2", "properties") + temp.write ' \n' + + DataMaskingInputStream stream = new DataMaskingInputStream(temp.newDataInputStream()) + def lines = stream.readLines() + assertTrue(lines.size() == 1) + + String line = lines[0].trim() + String expected = '' + assertEquals(line, expected) + + temp.deleteOnExit(); + } + + void testMySQLDBContent() + { + def temp = File.createTempFile("cfg2", "properties") + temp.write ' \n' + + DataMaskingInputStream stream = new DataMaskingInputStream(temp.newDataInputStream()) + def lines = stream.readLines() + assertTrue(lines.size() == 1) + + String line = lines[0].trim() + String expected = '' + assertEquals(line, expected) + + temp.deleteOnExit(); + } - void testOracleDBContent() { - def temp = File.createTempFile("cfg2", "properties") - temp.write ' \n' - String line = new DataMaskingInputStream(temp.newDataInputStream()).readLines()[0] - String expected = '' - assertFalse (line.contains("AES/CBC/PKCS5Padding(3QIdAjOKfAqcetGKhHEWez")) - temp.deleteOnExit(); - } - - void testMySQLDBContent() { - def temp = File.createTempFile("cfg2", "properties") - temp.write ' \n' - - String line = new DataMaskingInputStream(temp.newDataInputStream()).readLines()[0] - String expected = '' - assertFalse(line.contains("password=test")) - temp.deleteOnExit(); - } } diff --git a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy new file mode 100644 index 0000000..f88ae9e --- /dev/null +++ b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestGroovyIOUtils.groovy @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2011 Yan Pujante + * + * Licensed 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 test.util.io + +import org.linkedin.groovy.util.io.fs.FileSystemImpl +import org.linkedin.groovy.util.io.fs.FileSystem +import org.linkedin.util.io.resource.Resource +import org.linkedin.groovy.util.io.GroovyIOUtils + +import org.linkedin.groovy.util.net.GroovyNetUtils +import com.sun.net.httpserver.HttpExchange +import com.sun.net.httpserver.Headers + +/** + * @author yan@pongasoft.com */ +public class TestGroovyIOUtils extends GroovyTestCase +{ + public void testCat() + { + FileSystemImpl.createTempFileSystem { FileSystem fs -> + + Resource r1 = fs.saveContent('/dir/file1', 'abcd') + + assertEquals('abcd', GroovyIOUtils.cat(r1.toURI())) + assertEquals('abcd', GroovyIOUtils.cat(r1)) + assertEquals('abcd', GroovyIOUtils.cat(r1.file.canonicalPath)) + } + } + + public void testWithFile() + { + GroovyNetUtils.withHttpEchoServer { int port -> + FileSystemImpl.createTempFileSystem { FileSystem fs -> + assertEquals("abcd", GroovyIOUtils.cat(new URI("http://localhost:${port}/echo?msg=abcd"))) + Resource r1 = fs.saveContent('/dir/file1', 'abcd') + + GroovyIOUtils.withFile(r1) { File f -> + assertEquals(r1.file, f) + assertEquals("abcd", f.text) + } + + GroovyIOUtils.withFile(r1.toURI()) { File f -> + assertEquals(r1.file, f) + assertEquals("abcd", f.text) + } + + GroovyIOUtils.withFile(r1.file) { File f -> + assertEquals(r1.file, f) + assertEquals("abcd", f.text) + } + + GroovyIOUtils.withFile(r1.file.canonicalPath) { File f -> + assertEquals(r1.file, f) + assertEquals("abcd", f.text) + } + + GroovyIOUtils.withFile(null) { File f -> + assertNull(f) + } + + // this pattern is exactly the kind of pattern that should *not* be followed! + // here I am testing that once the closure is over the file is properly deleted + File tempFile = null + + GroovyIOUtils.withFile("http://localhost:${port}/echo?msg=abcd") { File f -> + tempFile = f + assertTrue(tempFile.exists()) + assertEquals("abcd", f.text) + } + + assertFalse(tempFile.exists()) + + // r1 should still exists + assertTrue(r1.exists()) + } + } + } + + /** + * Verifys that testSafeOverwrite properly works + */ + public void testSafeOverwrite() + { + FileSystemImpl.createTempFileSystem { FileSystem fs -> + File root = fs.root.file + + File tmpFile = null + GroovyIOUtils.safeOverwrite(new File(root, "foo.txt")) { File f -> + tmpFile = f + + // should be a non existent file + assertFalse(f.exists()) + + // should be in the same folder as 'foo.txt' + assertEquals(root, f.parentFile) + assertTrue(f.name.startsWith('++tmp.')) + assertTrue(f.name.endsWith('.tmp++')) + + f.text = "abcd" + } + // after the call the file should be deleted + assertFalse(tmpFile.exists()) + + // the file foo.txt should have been created + assertEquals("abcd", new File(root, "foo.txt").text) + + // error cases + File foo2 = new File(root, "foo2.txt") + foo2.text = 'defg' + + assertEquals("forcing: klmn", shouldFail(Exception.class) { + GroovyIOUtils.safeOverwrite(foo2) { File f -> + tmpFile = f + f.text = 'klmn' + throw new Exception('forcing: klmn') + } + }) + + // after the call the file should be deleted + assertFalse(tmpFile.exists()) + + // the original file should be the same + assertEquals('defg', foo2.text) + + // if file is never written, it means the original should not exist + GroovyIOUtils.safeOverwrite(foo2) { File f -> + tmpFile = f + // don't do anything with f + } + + // after the call the file should be deleted + assertFalse(tmpFile.exists()) + + // the original file should be the same + assertEquals('defg', foo2.text) + + // now test for factory + GroovyIOUtils.safeOverwrite(foo2, {File f -> new File(f.parentFile, "lolo")}) { File f -> + tmpFile = f + assertEquals(new File(root, 'lolo'), f) + f.text = 'hijk' + } + + // after the call the file should be deleted + assertFalse(tmpFile.exists()) + + // the original file should have been updated + assertEquals('hijk', foo2.text) + } + } + + /** + * Test for fetch content. Make sure that authorization works properly + */ + public void testFetchContent() + { + String response + Headers requestHeaders + + def handler = { HttpExchange t -> + requestHeaders = t.requestHeaders + t.sendResponseHeaders(200, response.length()); + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + + GroovyNetUtils.withHttpServer(0, ['/content': handler]) { int port -> + + FileSystemImpl.createTempFileSystem { FileSystem fs -> + File root = fs.root.file + + File tmpFile = new File(root, 'foo.txt') + + response = "abc" + + GroovyIOUtils.fetchContent("http://localhost:${port}/content", tmpFile) + assertEquals("abc", tmpFile.text) + // no authorization header should be present! + assertFalse(requestHeaders.containsKey('Authorization')) + + response = "def" + GroovyIOUtils.fetchContent("http://u1:p1@localhost:${port}/content", tmpFile) + assertEquals("def", tmpFile.text) + // authorization header should be present and properly base64ified + assertEquals("Basic ${'u1:p1'.bytes.encodeBase64()}", + requestHeaders['Authorization'].iterator().next()) + } + } + } +} From b861d841c92bec444f85d4eb3028d5a978c3f63c Mon Sep 17 00:00:00 2001 From: Zoran Simic Date: Mon, 2 Jul 2012 13:24:59 -0700 Subject: [PATCH 11/12] Ignoring generated 'logs' folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d557c95..ff93777 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ *.iws build out +logs From 0d50e22b992325cf5083b704e6394a9b1e44da04 Mon Sep 17 00:00:00 2001 From: Zoran Simic Date: Mon, 2 Jul 2012 15:20:21 -0700 Subject: [PATCH 12/12] Corrected TestDataMaskingInputStream --- .../util/io/TestDataMaskingInputStream.groovy | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy index d804d9a..24fadd1 100644 --- a/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy +++ b/org.linkedin.util-groovy/src/test/groovy/test/util/io/TestDataMaskingInputStream.groovy @@ -28,34 +28,30 @@ class TestDataMaskingInputStream extends GroovyTestCase { void testOracleDBContent() { - def temp = File.createTempFile("cfg2", "properties") - temp.write ' \n' + def input = ' \n' - DataMaskingInputStream stream = new DataMaskingInputStream(temp.newDataInputStream()) + DataMaskingInputStream stream = new DataMaskingInputStream(new ByteArrayInputStream(input.getBytes("UTF-8"))) def lines = stream.readLines() + stream.close() assertTrue(lines.size() == 1) String line = lines[0].trim() String expected = '' assertEquals(line, expected) - - temp.deleteOnExit(); } void testMySQLDBContent() { - def temp = File.createTempFile("cfg2", "properties") - temp.write ' \n' + def input = ' \n' - DataMaskingInputStream stream = new DataMaskingInputStream(temp.newDataInputStream()) + DataMaskingInputStream stream = new DataMaskingInputStream(new ByteArrayInputStream(input.getBytes("UTF-8"))) def lines = stream.readLines() + stream.close() assertTrue(lines.size() == 1) String line = lines[0].trim() String expected = '' assertEquals(line, expected) - - temp.deleteOnExit(); } }