diff --git a/spring-core/src/main/java/org/springframework/util/Base64Utils.java b/spring-core/src/main/java/org/springframework/util/Base64Utils.java index ec6aaa6e16b..ecdb9a56309 100644 --- a/spring-core/src/main/java/org/springframework/util/Base64Utils.java +++ b/spring-core/src/main/java/org/springframework/util/Base64Utils.java @@ -18,21 +18,25 @@ package org.springframework.util; import java.nio.charset.Charset; import java.util.Base64; +import javax.xml.bind.DatatypeConverter; import org.springframework.lang.UsesJava8; /** * A simple utility class for Base64 encoding and decoding. * - *
Adapts to either Java 8's {@link java.util.Base64} class or Apache - * Commons Codec's {@link org.apache.commons.codec.binary.Base64} class. - * With neither Java 8 nor Commons Codec present, encode/decode calls - * will fail with an IllegalStateException. + *
Adapts to either Java 8's {@link java.util.Base64} class or Apache Commons Codec's + * {@link org.apache.commons.codec.binary.Base64} class. With neither Java 8 nor Commons + * Codec present, {@link #encode}/{@link #decode} calls will throw an IllegalStateException. + * However, as of Spring 4.2, {@link #encodeToString} and {@link #decodeFromString} will + * nevertheless work since they can delegate to the JAXB DatatypeConverter as a fallback. * * @author Juergen Hoeller * @since 4.1 * @see java.util.Base64 * @see org.apache.commons.codec.binary.Base64 + * @see javax.xml.bind.DatatypeConverter#printBase64Binary + * @see javax.xml.bind.DatatypeConverter#parseBase64Binary */ public abstract class Base64Utils { @@ -55,11 +59,12 @@ public abstract class Base64Utils { } /** - * Assert that Byte64 encoding is actually supported. + * Assert that Byte64 encoding between byte arrays is actually supported. * @throws IllegalStateException if neither Java 8 nor Apache Commons Codec is present */ - private static void assertSupported() { - Assert.state(delegate != null, "Neither Java 8 nor Apache Commons Codec found - Base64 encoding not supported"); + private static void assertDelegateAvailable() { + Assert.state(delegate != null, + "Neither Java 8 nor Apache Commons Codec found - Base64 encoding between byte arrays not supported"); } @@ -67,61 +72,71 @@ public abstract class Base64Utils { * Base64-encode the given byte array. * @param src the original byte array (may be {@code null}) * @return the encoded byte array (or {@code null} if the input was {@code null}) - * @throws IllegalStateException if Base64 encoding is not supported, - * i.e. neither Java 8 nor Apache Commons Codec is present at runtime + * @throws IllegalStateException if Base64 encoding between byte arrays is not + * supported, i.e. neither Java 8 nor Apache Commons Codec is present at runtime */ public static byte[] encode(byte[] src) { - assertSupported(); + assertDelegateAvailable(); return delegate.encode(src); } + /** + * Base64-decode the given byte array. + * @param src the encoded byte array (may be {@code null}) + * @return the original byte array (or {@code null} if the input was {@code null}) + * @throws IllegalStateException if Base64 encoding between byte arrays is not + * supported, i.e. neither Java 8 nor Apache Commons Codec is present at runtime + */ + public static byte[] decode(byte[] src) { + assertDelegateAvailable(); + return delegate.decode(src); + } + /** * Base64-encode the given byte array to a String. * @param src the original byte array (may be {@code null}) * @return the encoded byte array as a UTF-8 String * (or {@code null} if the input was {@code null}) - * @throws IllegalStateException if Base64 encoding is not supported, - * i.e. neither Java 8 nor Apache Commons Codec is present at runtime */ public static String encodeToString(byte[] src) { - assertSupported(); if (src == null) { return null; } if (src.length == 0) { return ""; } - return new String(delegate.encode(src), DEFAULT_CHARSET); - } - /** - * Base64-decode the given byte array. - * @param src the encoded byte array (may be {@code null}) - * @return the original byte array (or {@code null} if the input was {@code null}) - * @throws IllegalStateException if Base64 encoding is not supported, - * i.e. neither Java 8 nor Apache Commons Codec is present at runtime - */ - public static byte[] decode(byte[] src) { - assertSupported(); - return delegate.decode(src); + if (delegate != null) { + // Full encoder available + return new String(delegate.encode(src), DEFAULT_CHARSET); + } + else { + // JAXB fallback for String case + return DatatypeConverter.printBase64Binary(src); + } } /** * Base64-decode the given byte array from an UTF-8 String. * @param src the encoded UTF-8 String (may be {@code null}) * @return the original byte array (or {@code null} if the input was {@code null}) - * @throws IllegalStateException if Base64 encoding is not supported, - * i.e. neither Java 8 nor Apache Commons Codec is present at runtime */ public static byte[] decodeFromString(String src) { - assertSupported(); if (src == null) { return null; } if (src.length() == 0) { return new byte[0]; } - return delegate.decode(src.getBytes(DEFAULT_CHARSET)); + + if (delegate != null) { + // Full encoder available + return delegate.decode(src.getBytes(DEFAULT_CHARSET)); + } + else { + // JAXB fallback for String case + return DatatypeConverter.parseBase64Binary(src); + } } diff --git a/spring-core/src/test/java/org/springframework/util/Base64UtilsTests.java b/spring-core/src/test/java/org/springframework/util/Base64UtilsTests.java index 357707d641c..a86372e47bf 100644 --- a/spring-core/src/test/java/org/springframework/util/Base64UtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/Base64UtilsTests.java @@ -18,32 +18,67 @@ package org.springframework.util; import java.io.UnsupportedEncodingException; +import javax.xml.bind.DatatypeConverter; + import org.junit.Test; import static org.junit.Assert.*; /** * @author Juergen Hoeller + * @since 4.2 */ public class Base64UtilsTests { @Test - public void jdk8VsCommonsCodec() throws UnsupportedEncodingException { + public void encodeWithJdk8VsCommonsCodec() throws UnsupportedEncodingException { Base64Utils.Base64Delegate jdkDelegate = new Base64Utils.JdkBase64Delegate(); Base64Utils.Base64Delegate commonsDelegate = new Base64Utils.CommonsCodecBase64Delegate(); byte[] bytes = new byte[] {-0x4f, 0xa, -0x73, -0x4f, 0x64, -0x20, 0x75, 0x41, 0x5, -0x49, -0x57, -0x65, -0x19, 0x2e, 0x3f, -0x1b}; assertArrayEquals(jdkDelegate.encode(bytes), commonsDelegate.encode(bytes)); + assertArrayEquals(bytes, jdkDelegate.decode(jdkDelegate.encode(bytes))); + assertArrayEquals(bytes, commonsDelegate.decode(commonsDelegate.encode(bytes))); bytes = "Hello World".getBytes("UTF-8"); assertArrayEquals(jdkDelegate.encode(bytes), commonsDelegate.encode(bytes)); + assertArrayEquals(bytes, jdkDelegate.decode(jdkDelegate.encode(bytes))); + assertArrayEquals(bytes, commonsDelegate.decode(commonsDelegate.encode(bytes))); bytes = "Hello World\r\nSecond Line".getBytes("UTF-8"); assertArrayEquals(jdkDelegate.encode(bytes), commonsDelegate.encode(bytes)); + assertArrayEquals(bytes, jdkDelegate.decode(jdkDelegate.encode(bytes))); + assertArrayEquals(bytes, commonsDelegate.decode(commonsDelegate.encode(bytes))); bytes = "Hello World\r\nSecond Line\r\n".getBytes("UTF-8"); assertArrayEquals(jdkDelegate.encode(bytes), commonsDelegate.encode(bytes)); + assertArrayEquals(bytes, jdkDelegate.decode(jdkDelegate.encode(bytes))); + assertArrayEquals(bytes, commonsDelegate.decode(commonsDelegate.encode(bytes))); + } + + @Test + public void encodeToStringWithJdk8VsJaxb() throws UnsupportedEncodingException { + byte[] bytes = new byte[] + {-0x4f, 0xa, -0x73, -0x4f, 0x64, -0x20, 0x75, 0x41, 0x5, -0x49, -0x57, -0x65, -0x19, 0x2e, 0x3f, -0x1b}; + assertEquals(Base64Utils.encodeToString(bytes), DatatypeConverter.printBase64Binary(bytes)); + assertArrayEquals(bytes, Base64Utils.decodeFromString(Base64Utils.encodeToString(bytes))); + assertArrayEquals(bytes, DatatypeConverter.parseBase64Binary(DatatypeConverter.printBase64Binary(bytes))); + + bytes = "Hello World".getBytes("UTF-8"); + assertEquals(Base64Utils.encodeToString(bytes), DatatypeConverter.printBase64Binary(bytes)); + assertArrayEquals(bytes, Base64Utils.decodeFromString(Base64Utils.encodeToString(bytes))); + assertArrayEquals(bytes, DatatypeConverter.parseBase64Binary(DatatypeConverter.printBase64Binary(bytes))); + + bytes = "Hello World\r\nSecond Line".getBytes("UTF-8"); + assertEquals(Base64Utils.encodeToString(bytes), DatatypeConverter.printBase64Binary(bytes)); + assertArrayEquals(bytes, Base64Utils.decodeFromString(Base64Utils.encodeToString(bytes))); + assertArrayEquals(bytes, DatatypeConverter.parseBase64Binary(DatatypeConverter.printBase64Binary(bytes))); + + bytes = "Hello World\r\nSecond Line\r\n".getBytes("UTF-8"); + assertEquals(Base64Utils.encodeToString(bytes), DatatypeConverter.printBase64Binary(bytes)); + assertArrayEquals(bytes, Base64Utils.decodeFromString(Base64Utils.encodeToString(bytes))); + assertArrayEquals(bytes, DatatypeConverter.parseBase64Binary(DatatypeConverter.printBase64Binary(bytes))); } } diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/GsonBuilderUtils.java b/spring-web/src/main/java/org/springframework/http/converter/json/GsonBuilderUtils.java index 881f0f59235..54aa0e08416 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/GsonBuilderUtils.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/GsonBuilderUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,10 +52,6 @@ public abstract class GsonBuilderUtils { * On Java 8, the standard {@link java.util.Base64} facility is used instead. */ public static GsonBuilder gsonBuilderWithBase64EncodedByteArrays() { - // Assert that Base64 support is available, as long we're not on Java 8+ - Base64Utils.encode(null); - - // Now, construct a pre-configured GsonBuilder... GsonBuilder builder = new GsonBuilder(); builder.registerTypeHierarchyAdapter(byte[].class, new Base64TypeAdapter()); return builder;