|
@@ -30,8 +30,15 @@ import com.google.gson.JsonPrimitive;
|
|
import com.google.gson.JsonSerializationContext;
|
|
import com.google.gson.JsonSerializationContext;
|
|
import com.google.gson.JsonSerializer;
|
|
import com.google.gson.JsonSerializer;
|
|
import com.google.gson.reflect.TypeToken;
|
|
import com.google.gson.reflect.TypeToken;
|
|
|
|
+import com.squareup.moshi.JsonAdapter;
|
|
|
|
+import com.squareup.moshi.JsonReader;
|
|
|
|
+import com.squareup.moshi.JsonWriter;
|
|
|
|
+import com.squareup.moshi.Moshi;
|
|
|
|
+import com.squareup.moshi.Types;
|
|
|
|
+import org.jetbrains.annotations.Nullable;
|
|
import password.pwm.PwmConstants;
|
|
import password.pwm.PwmConstants;
|
|
import password.pwm.bean.DomainID;
|
|
import password.pwm.bean.DomainID;
|
|
|
|
+import password.pwm.error.PwmInternalException;
|
|
import password.pwm.error.PwmUnrecoverableException;
|
|
import password.pwm.error.PwmUnrecoverableException;
|
|
import password.pwm.ldap.PwmLdapVendor;
|
|
import password.pwm.ldap.PwmLdapVendor;
|
|
import password.pwm.util.PasswordData;
|
|
import password.pwm.util.PasswordData;
|
|
@@ -51,6 +58,7 @@ import java.util.Collection;
|
|
import java.util.Date;
|
|
import java.util.Date;
|
|
import java.util.List;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
|
+import java.util.Objects;
|
|
import java.util.TimeZone;
|
|
import java.util.TimeZone;
|
|
import java.util.concurrent.atomic.LongAdder;
|
|
import java.util.concurrent.atomic.LongAdder;
|
|
|
|
|
|
@@ -61,352 +69,819 @@ public class JsonUtil
|
|
public enum Flag
|
|
public enum Flag
|
|
{
|
|
{
|
|
PrettyPrint,
|
|
PrettyPrint,
|
|
- HtmlEscape,
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- private static final Gson GENERIC_GSON = registerTypeAdapters( new GsonBuilder() )
|
|
|
|
- .disableHtmlEscaping()
|
|
|
|
- .create();
|
|
|
|
-
|
|
|
|
- private static Gson getGson( final Flag... flags )
|
|
|
|
|
|
+ interface PwmJsonServiceProvider
|
|
{
|
|
{
|
|
- if ( flags == null || flags.length == 0 )
|
|
|
|
- {
|
|
|
|
- return GENERIC_GSON;
|
|
|
|
- }
|
|
|
|
|
|
+ <T> T deserialize( String json, Class<T> classOfT );
|
|
|
|
|
|
- final GsonBuilder gsonBuilder = registerTypeAdapters( new GsonBuilder() );
|
|
|
|
|
|
+ <T> T deserialize( String jsonString, TypeToken typeToken );
|
|
|
|
|
|
- if ( !JavaHelper.enumArrayContainsValue( flags, Flag.HtmlEscape ) )
|
|
|
|
- {
|
|
|
|
- gsonBuilder.disableHtmlEscaping();
|
|
|
|
- }
|
|
|
|
|
|
+ <T> T deserialize( String jsonString, Type type );
|
|
|
|
|
|
- if ( JavaHelper.enumArrayContainsValue( flags, Flag.PrettyPrint ) )
|
|
|
|
- {
|
|
|
|
- gsonBuilder.setPrettyPrinting();
|
|
|
|
- }
|
|
|
|
|
|
+ List<String> deserializeStringList( String jsonString );
|
|
|
|
+
|
|
|
|
+ Map<String, String> deserializeStringMap( String jsonString );
|
|
|
|
|
|
- return gsonBuilder.create();
|
|
|
|
|
|
+ Map<String, Object> deserializeStringObjectMap( String jsonString );
|
|
|
|
+
|
|
|
|
+ Map<String, Object> deserializeMap( String jsonString );
|
|
|
|
+
|
|
|
|
+ <T> T deserialize( String jsonString, Class<T> classOfT, Type... parameterizedTypes );
|
|
|
|
+
|
|
|
|
+ String serialize( Serializable object, Flag... flags );
|
|
|
|
+
|
|
|
|
+ String serializeMap( Map object, Flag... flags );
|
|
|
|
+
|
|
|
|
+ String serializeCollection( Collection object, Flag... flags );
|
|
|
|
+
|
|
|
|
+ <T> T cloneUsingJson( Serializable srcObject, Class<T> classOfT );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private static final PwmJsonServiceProvider GSON_PROVIDER = new GsonPwmJsonServiceProvider();
|
|
|
|
+ private static final PwmJsonServiceProvider MOSHI_PROVIDER = new MoshiPwmJsonServiceProvider();
|
|
|
|
+ private static final PwmJsonServiceProvider PROVIDER = GSON_PROVIDER;
|
|
|
|
+
|
|
public static <T> T deserialize( final String jsonString, final TypeToken typeToken )
|
|
public static <T> T deserialize( final String jsonString, final TypeToken typeToken )
|
|
{
|
|
{
|
|
- return JsonUtil.getGson().fromJson( jsonString, typeToken.getType() );
|
|
|
|
|
|
+ return PROVIDER.deserialize( jsonString, typeToken );
|
|
}
|
|
}
|
|
|
|
|
|
public static <T> T deserialize( final String jsonString, final Type type )
|
|
public static <T> T deserialize( final String jsonString, final Type type )
|
|
{
|
|
{
|
|
- return JsonUtil.getGson().fromJson( jsonString, type );
|
|
|
|
|
|
+ return PROVIDER.deserialize( jsonString, type );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public static <T> T deserialize( final String json, final Class<T> classOfT )
|
|
|
|
+ {
|
|
|
|
+ return PROVIDER.deserialize( json, classOfT );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public static <T> T deserialize( final String json, final Class<T> classOfT, final Type... parameterizedTypes )
|
|
|
|
+ {
|
|
|
|
+ return PROVIDER.deserialize( json, classOfT, parameterizedTypes );
|
|
}
|
|
}
|
|
|
|
|
|
public static List<String> deserializeStringList( final String jsonString )
|
|
public static List<String> deserializeStringList( final String jsonString )
|
|
{
|
|
{
|
|
- return JsonUtil.getGson().fromJson( jsonString, new TypeToken<List<Object>>()
|
|
|
|
- {
|
|
|
|
- }.getType() );
|
|
|
|
|
|
+ return PROVIDER.deserializeStringList( jsonString );
|
|
}
|
|
}
|
|
|
|
|
|
public static Map<String, String> deserializeStringMap( final String jsonString )
|
|
public static Map<String, String> deserializeStringMap( final String jsonString )
|
|
{
|
|
{
|
|
- return JsonUtil.getGson().fromJson( jsonString, new TypeToken<Map<String, String>>()
|
|
|
|
- {
|
|
|
|
- }.getType() );
|
|
|
|
|
|
+ return PROVIDER.deserializeStringMap( jsonString );
|
|
}
|
|
}
|
|
|
|
|
|
public static Map<String, Object> deserializeStringObjectMap( final String jsonString )
|
|
public static Map<String, Object> deserializeStringObjectMap( final String jsonString )
|
|
{
|
|
{
|
|
- return JsonUtil.getGson().fromJson( jsonString, new TypeToken<Map<String, Object>>()
|
|
|
|
- {
|
|
|
|
- }.getType() );
|
|
|
|
|
|
+ return PROVIDER.deserializeStringObjectMap( jsonString );
|
|
}
|
|
}
|
|
|
|
|
|
public static Map<String, Object> deserializeMap( final String jsonString )
|
|
public static Map<String, Object> deserializeMap( final String jsonString )
|
|
{
|
|
{
|
|
- return JsonUtil.getGson().fromJson( jsonString, new TypeToken<Map<String, Object>>()
|
|
|
|
- {
|
|
|
|
- }.getType() );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public static <T> T deserialize( final String json, final Class<T> classOfT )
|
|
|
|
- {
|
|
|
|
- return JsonUtil.getGson().fromJson( json, classOfT );
|
|
|
|
|
|
+ return PROVIDER.deserializeMap( jsonString );
|
|
}
|
|
}
|
|
|
|
|
|
public static String serialize( final Serializable object, final Flag... flags )
|
|
public static String serialize( final Serializable object, final Flag... flags )
|
|
{
|
|
{
|
|
- return JsonUtil.getGson( flags ).toJson( object );
|
|
|
|
|
|
+ return PROVIDER.serialize( object, flags );
|
|
}
|
|
}
|
|
|
|
|
|
public static String serializeMap( final Map object, final Flag... flags )
|
|
public static String serializeMap( final Map object, final Flag... flags )
|
|
{
|
|
{
|
|
- return JsonUtil.getGson( flags ).toJson( object );
|
|
|
|
|
|
+ return PROVIDER.serializeMap( object, flags );
|
|
}
|
|
}
|
|
|
|
|
|
public static String serializeCollection( final Collection object, final Flag... flags )
|
|
public static String serializeCollection( final Collection object, final Flag... flags )
|
|
{
|
|
{
|
|
- return JsonUtil.getGson( flags ).toJson( object );
|
|
|
|
|
|
+ return PROVIDER.serializeCollection( object, flags );
|
|
}
|
|
}
|
|
|
|
|
|
- /**
|
|
|
|
- * Gson Serializer for {@link java.security.cert.X509Certificate}. Necessary because sometimes X509Certs have circular references
|
|
|
|
- * and the default gson serializer will cause a {@code java.lang.StackOverflowError}. Standard Base64 encoding of
|
|
|
|
- * the cert is used as the json format.
|
|
|
|
- */
|
|
|
|
- private static class X509CertificateAdapter implements JsonSerializer<X509Certificate>, JsonDeserializer<X509Certificate>
|
|
|
|
|
|
+ public static <T> T cloneUsingJson( final Serializable srcObject, final Class<T> classOfT )
|
|
|
|
+ {
|
|
|
|
+ return PROVIDER.cloneUsingJson( srcObject, classOfT );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static class MoshiPwmJsonServiceProvider implements PwmJsonServiceProvider
|
|
{
|
|
{
|
|
- private X509CertificateAdapter( )
|
|
|
|
|
|
+ private static final Moshi GENERIC_MOSHI = getMoshi();
|
|
|
|
+
|
|
|
|
+ private static Moshi getMoshi( final Flag... flags )
|
|
{
|
|
{
|
|
|
|
+ if ( GENERIC_MOSHI != null && ( flags == null || flags.length <= 0 ) )
|
|
|
|
+ {
|
|
|
|
+ return GENERIC_MOSHI;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ final Moshi.Builder moshiBuilder = new Moshi.Builder();
|
|
|
|
+ registerTypeAdapters( moshiBuilder );
|
|
|
|
+ return moshiBuilder.build();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static <T> JsonAdapter<T> applyFlagsToAdapter( final JsonAdapter<T> adapter, final Flag... flags )
|
|
|
|
+ {
|
|
|
|
+ JsonAdapter<T> adapterInProgress = adapter;
|
|
|
|
+
|
|
|
|
+ if ( JavaHelper.enumArrayContainsValue( flags, Flag.PrettyPrint ) )
|
|
|
|
+ {
|
|
|
|
+ adapterInProgress = adapter.indent( " " );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return adapterInProgress;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static void registerTypeAdapters( final Moshi.Builder moshiBuilder, final Flag... flags )
|
|
|
|
+ {
|
|
|
|
+ moshiBuilder.add( Date.class, applyFlagsToAdapter( new DateTypeAdapter(), flags ) );
|
|
|
|
+ moshiBuilder.add( Instant.class, applyFlagsToAdapter( new InstantTypeAdapter(), flags ) );
|
|
|
|
+ moshiBuilder.add( X509Certificate.class, applyFlagsToAdapter( new X509CertificateAdapter(), flags ) );
|
|
|
|
+ moshiBuilder.add( PasswordData.class, applyFlagsToAdapter( new PasswordDataAdapter(), flags ) );
|
|
|
|
+ moshiBuilder.add( DomainID.class, applyFlagsToAdapter( new DomainIdAdaptor(), flags ) );
|
|
|
|
+ moshiBuilder.add( LongAdder.class, applyFlagsToAdapter( new LongAdderTypeAdaptor(), flags ) );
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
- public JsonElement serialize( final X509Certificate cert, final Type type, final JsonSerializationContext jsonSerializationContext )
|
|
|
|
|
|
+ public List<String> deserializeStringList( final String jsonString )
|
|
{
|
|
{
|
|
|
|
+ final Moshi moshi = getMoshi();
|
|
|
|
+ final Type type = Types.newParameterizedType( List.class, String.class );
|
|
|
|
+ final JsonAdapter<List<String>> adapter = moshi.adapter( type );
|
|
|
|
+
|
|
try
|
|
try
|
|
{
|
|
{
|
|
- return new JsonPrimitive( StringUtil.base64Encode( cert.getEncoded() ) );
|
|
|
|
|
|
+ return List.copyOf( Objects.requireNonNull( adapter.fromJson( jsonString ) ) );
|
|
}
|
|
}
|
|
- catch ( final PwmUnrecoverableException | CertificateEncodingException e )
|
|
|
|
|
|
+ catch ( final IOException e )
|
|
{
|
|
{
|
|
- throw new IllegalStateException( "unable to json-encode certificate: " + e.getMessage() );
|
|
|
|
|
|
+ throw new RuntimeException( e.getMessage() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
- public X509Certificate deserialize( final JsonElement jsonElement, final Type type, final JsonDeserializationContext jsonDeserializationContext )
|
|
|
|
- throws JsonParseException
|
|
|
|
|
|
+ public <T> T deserialize( final String jsonString, final Type type )
|
|
{
|
|
{
|
|
|
|
+ final Moshi moshi = getMoshi();
|
|
|
|
+ final JsonAdapter<T> adapter = moshi.adapter( type );
|
|
|
|
+
|
|
try
|
|
try
|
|
{
|
|
{
|
|
- final CertificateFactory certificateFactory = CertificateFactory.getInstance( "X.509" );
|
|
|
|
- return ( X509Certificate ) certificateFactory.generateCertificate( new ByteArrayInputStream( StringUtil.base64Decode(
|
|
|
|
- jsonElement.getAsString() ) ) );
|
|
|
|
|
|
+ return adapter.fromJson( jsonString );
|
|
}
|
|
}
|
|
- catch ( final Exception e )
|
|
|
|
|
|
+ catch ( final IOException e )
|
|
{
|
|
{
|
|
- throw new JsonParseException( "unable to parse x509certificate: " + e.getMessage() );
|
|
|
|
|
|
+ throw new RuntimeException( e.getMessage() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- /**
|
|
|
|
- * GsonSerializer that stores dates in ISO 8601 format, with a deserialier that also reads local-platform format reading.
|
|
|
|
- */
|
|
|
|
- private static class DateTypeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date>
|
|
|
|
- {
|
|
|
|
- private static final PwmDateFormat ISO_DATE_FORMAT = PwmDateFormat.newPwmDateFormat(
|
|
|
|
- "yyyy-MM-dd'T'HH:mm:ss'Z'",
|
|
|
|
- PwmConstants.DEFAULT_LOCALE,
|
|
|
|
- TimeZone.getTimeZone( "Zulu" ) );
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public Map<String, String> deserializeStringMap( final String jsonString )
|
|
|
|
+ {
|
|
|
|
+ final Type type = Types.newParameterizedType( Map.class, String.class, String.class );
|
|
|
|
+ return Map.copyOf( deserialize( jsonString, type ) );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public Map<String, Object> deserializeStringObjectMap( final String jsonString )
|
|
|
|
+ {
|
|
|
|
+ final Type type = Types.newParameterizedType( Map.class, String.class, String.class );
|
|
|
|
+ return Map.copyOf( deserialize( jsonString, type ) );
|
|
|
|
+ }
|
|
|
|
|
|
- private DateFormat getGsonDateFormat()
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public Map<String, Object> deserializeMap( final String jsonString )
|
|
{
|
|
{
|
|
- final DateFormat gsonDateFormat = DateFormat.getDateTimeInstance( DateFormat.DEFAULT, DateFormat.DEFAULT );
|
|
|
|
- gsonDateFormat.setTimeZone( TimeZone.getDefault() );
|
|
|
|
- return gsonDateFormat;
|
|
|
|
|
|
+ final Type type = Types.newParameterizedType( Map.class, String.class, Object.class );
|
|
|
|
+ return Map.copyOf( deserialize( jsonString, type ) );
|
|
}
|
|
}
|
|
|
|
|
|
- private DateTypeAdapter( )
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public <T> T deserialize( final String jsonString, final Class<T> classOfT )
|
|
{
|
|
{
|
|
|
|
+ final Type type = Types.supertypeOf( classOfT );
|
|
|
|
+ return deserialize( jsonString, type );
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
- public JsonElement serialize( final Date date, final Type type, final JsonSerializationContext jsonSerializationContext )
|
|
|
|
|
|
+ public <T> T deserialize( final String jsonString, final TypeToken typeToken )
|
|
{
|
|
{
|
|
- return new JsonPrimitive( ISO_DATE_FORMAT.format( date.toInstant() ) );
|
|
|
|
|
|
+ final Type type = Types.newParameterizedType( typeToken.getRawType() );
|
|
|
|
+ return deserialize( jsonString, type );
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
- public Date deserialize( final JsonElement jsonElement, final Type type, final JsonDeserializationContext jsonDeserializationContext )
|
|
|
|
|
|
+ public <T> T deserialize( final String jsonString, final Class<T> classOfT, final Type... parameterizedTypes )
|
|
{
|
|
{
|
|
- try
|
|
|
|
- {
|
|
|
|
- return Date.from( ISO_DATE_FORMAT.parse( jsonElement.getAsString() ) );
|
|
|
|
- }
|
|
|
|
- catch ( final ParseException e )
|
|
|
|
|
|
+ final Type type = Types.newParameterizedType( classOfT, parameterizedTypes );
|
|
|
|
+ return deserialize( jsonString, type );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private <T> String serialize( final T object, final Type type, final Flag... flags )
|
|
|
|
+ {
|
|
|
|
+ final Moshi moshi = getMoshi();
|
|
|
|
+ final JsonAdapter<T> jsonAdapter = applyFlagsToAdapter( moshi.adapter( type ), flags );
|
|
|
|
+ return jsonAdapter.toJson( object );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public String serialize( final Serializable object, final Flag... flags )
|
|
|
|
+ {
|
|
|
|
+ final Type type;
|
|
|
|
+ if ( object instanceof Collection )
|
|
{
|
|
{
|
|
- /* noop */
|
|
|
|
|
|
+ type = Collection.class;
|
|
}
|
|
}
|
|
-
|
|
|
|
- // for backwards compatibility
|
|
|
|
- try
|
|
|
|
|
|
+ else if ( object instanceof Map )
|
|
{
|
|
{
|
|
- return getGsonDateFormat().parse( jsonElement.getAsString() );
|
|
|
|
|
|
+ type = Map.class;
|
|
}
|
|
}
|
|
- catch ( final ParseException e )
|
|
|
|
|
|
+ else
|
|
{
|
|
{
|
|
- LOGGER.debug( () -> "unable to parse stored json Date.class timestamp '" + jsonElement.getAsString() + "' error: " + e.getMessage() );
|
|
|
|
- throw new JsonParseException( e );
|
|
|
|
|
|
+ type = object.getClass();
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ return serialize( object, type, flags );
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- /**
|
|
|
|
- * GsonSerializer that stores instants in ISO 8601 format, with a deserialier that also reads local-platform format reading.
|
|
|
|
- */
|
|
|
|
- private static class InstantTypeAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant>
|
|
|
|
- {
|
|
|
|
- private InstantTypeAdapter( )
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public String serializeMap( final Map object, final Flag... flags )
|
|
{
|
|
{
|
|
|
|
+ final Type type = Types.newParameterizedType( Map.class );
|
|
|
|
+ return serialize( object, type, flags );
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
- public JsonElement serialize( final Instant instant, final Type type, final JsonSerializationContext jsonSerializationContext )
|
|
|
|
|
|
+ public String serializeCollection( final Collection object, final Flag... flags )
|
|
{
|
|
{
|
|
- return new JsonPrimitive( JavaHelper.toIsoDate( instant ) );
|
|
|
|
|
|
+ final Type type = Types.subtypeOf( Collection.class );
|
|
|
|
+ return serialize( object, type, flags );
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
- public Instant deserialize( final JsonElement jsonElement, final Type type, final JsonDeserializationContext jsonDeserializationContext )
|
|
|
|
|
|
+ public <T> T cloneUsingJson( final Serializable srcObject, final Class<T> classOfT )
|
|
{
|
|
{
|
|
- try
|
|
|
|
|
|
+ final String jsonObj = this.serialize( srcObject );
|
|
|
|
+ return this.deserialize( jsonObj, classOfT );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * GsonSerializer that stores instants in ISO 8601 format, with a deserializer that also reads local-platform format reading.
|
|
|
|
+ */
|
|
|
|
+ private static class InstantTypeAdapter extends JsonAdapter<Instant>
|
|
|
|
+ {
|
|
|
|
+ @Nullable
|
|
|
|
+ @Override
|
|
|
|
+ public Instant fromJson( final JsonReader reader ) throws IOException
|
|
{
|
|
{
|
|
- return JavaHelper.parseIsoToInstant( jsonElement.getAsString() );
|
|
|
|
|
|
+ final String strValue = reader.nextString();
|
|
|
|
+ if ( StringUtil.isEmpty( strValue ) )
|
|
|
|
+ {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ return JavaHelper.parseIsoToInstant( strValue );
|
|
|
|
+ }
|
|
|
|
+ catch ( final Exception e )
|
|
|
|
+ {
|
|
|
|
+ LOGGER.debug( () -> "unable to parse stored json Instant.class timestamp '" + strValue + "' error: " + e.getMessage() );
|
|
|
|
+ throw new IOException( e );
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- catch ( final Exception e )
|
|
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void toJson( final JsonWriter writer, @Nullable final Instant value ) throws IOException
|
|
{
|
|
{
|
|
- LOGGER.debug( () -> "unable to parse stored json Instant.class timestamp '" + jsonElement.getAsString() + "' error: " + e.getMessage() );
|
|
|
|
- throw new JsonParseException( e );
|
|
|
|
|
|
+ if ( value == null )
|
|
|
|
+ {
|
|
|
|
+ writer.jsonValue( "" );
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ writer.jsonValue( JavaHelper.toIsoDate( value ) );
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- private static class ByteArrayToBase64TypeAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]>
|
|
|
|
- {
|
|
|
|
- @Override
|
|
|
|
- public byte[] deserialize( final JsonElement json, final Type typeOfT, final JsonDeserializationContext context ) throws JsonParseException
|
|
|
|
|
|
+ /**
|
|
|
|
+ * GsonSerializer that stores dates in ISO 8601 format, with a deserializer that also reads local-platform format reading.
|
|
|
|
+ */
|
|
|
|
+ private static class DateTypeAdapter extends JsonAdapter<Date>
|
|
{
|
|
{
|
|
- try
|
|
|
|
|
|
+ private static final PwmDateFormat ISO_DATE_FORMAT = PwmDateFormat.newPwmDateFormat(
|
|
|
|
+ "yyyy-MM-dd'T'HH:mm:ss'Z'",
|
|
|
|
+ PwmConstants.DEFAULT_LOCALE,
|
|
|
|
+ TimeZone.getTimeZone( "Zulu" ) );
|
|
|
|
+
|
|
|
|
+ private static DateFormat getLegacyDateFormat()
|
|
{
|
|
{
|
|
- return StringUtil.base64Decode( json.getAsString() );
|
|
|
|
|
|
+ final DateFormat gsonDateFormat = DateFormat.getDateTimeInstance( DateFormat.DEFAULT, DateFormat.DEFAULT );
|
|
|
|
+ gsonDateFormat.setTimeZone( TimeZone.getDefault() );
|
|
|
|
+ return gsonDateFormat;
|
|
}
|
|
}
|
|
- catch ( final IOException e )
|
|
|
|
|
|
+
|
|
|
|
+ @Nullable
|
|
|
|
+ @Override
|
|
|
|
+ public Date fromJson( final JsonReader reader ) throws IOException
|
|
|
|
+ {
|
|
|
|
+ final String strValue = reader.nextString();
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ return Date.from( ISO_DATE_FORMAT.parse( strValue ) );
|
|
|
|
+ }
|
|
|
|
+ catch ( final ParseException e )
|
|
|
|
+ {
|
|
|
|
+ /* noop */
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // for backwards compatibility
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ return getLegacyDateFormat().parse( strValue );
|
|
|
|
+ }
|
|
|
|
+ catch ( final ParseException e )
|
|
|
|
+ {
|
|
|
|
+ LOGGER.debug( () -> "unable to parse stored json Date.class timestamp '" + strValue + "' error: " + e.getMessage() );
|
|
|
|
+ throw new IOException( e );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void toJson( final JsonWriter writer, @Nullable final Date value ) throws IOException
|
|
{
|
|
{
|
|
- final String errorMsg = "io stream error while de-serializing byte array: " + e.getMessage();
|
|
|
|
- LOGGER.error( () -> errorMsg );
|
|
|
|
- throw new JsonParseException( errorMsg, e );
|
|
|
|
|
|
+ Objects.requireNonNull( value );
|
|
|
|
+ writer.value( ISO_DATE_FORMAT.format( value.toInstant() ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
|
- public JsonElement serialize( final byte[] src, final Type typeOfSrc, final JsonSerializationContext context )
|
|
|
|
|
|
+ private static class DomainIdAdaptor extends JsonAdapter<DomainID>
|
|
{
|
|
{
|
|
- try
|
|
|
|
|
|
+ @Nullable
|
|
|
|
+ @Override
|
|
|
|
+ public DomainID fromJson( final JsonReader reader ) throws IOException
|
|
{
|
|
{
|
|
- return new JsonPrimitive( StringUtil.base64Encode( src, StringUtil.Base64Options.GZIP ) );
|
|
|
|
|
|
+ final String stringValue = reader.nextString();
|
|
|
|
+
|
|
|
|
+ if ( StringUtil.isEmpty( stringValue ) )
|
|
|
|
+ {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ( DomainID.systemId().toString().equals( stringValue ) )
|
|
|
|
+ {
|
|
|
|
+ return DomainID.systemId();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return DomainID.create( stringValue );
|
|
}
|
|
}
|
|
- catch ( final PwmUnrecoverableException e )
|
|
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void toJson( final JsonWriter writer, @Nullable final DomainID value ) throws IOException
|
|
{
|
|
{
|
|
- final String errorMsg = "error while JSON serializing byte array: " + e.getMessage();
|
|
|
|
- LOGGER.error( () -> errorMsg );
|
|
|
|
- throw new JsonParseException( errorMsg, e );
|
|
|
|
|
|
+ if ( value == null )
|
|
|
|
+ {
|
|
|
|
+ writer.nullValue();
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ writer.value( value.toString() );
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- private static class PasswordDataTypeAdapter implements JsonSerializer<PasswordData>, JsonDeserializer<PasswordData>
|
|
|
|
- {
|
|
|
|
- @Override
|
|
|
|
- public PasswordData deserialize( final JsonElement json, final Type typeOfT, final JsonDeserializationContext context ) throws JsonParseException
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Gson Serializer for {@link java.security.cert.X509Certificate}. Necessary because sometimes X509Certs have circular references
|
|
|
|
+ * and the default gson serializer will cause a {@code java.lang.StackOverflowError}. Standard Base64 encoding of
|
|
|
|
+ * the cert is used as the json format.
|
|
|
|
+ */
|
|
|
|
+ private static class X509CertificateAdapter extends JsonAdapter<X509Certificate>
|
|
{
|
|
{
|
|
- try
|
|
|
|
|
|
+ @Nullable
|
|
|
|
+ @Override
|
|
|
|
+ public X509Certificate fromJson( final JsonReader reader ) throws IOException
|
|
{
|
|
{
|
|
- return new PasswordData( json.getAsString() );
|
|
|
|
|
|
+ final String strValue = reader.nextString();
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ final CertificateFactory certificateFactory = CertificateFactory.getInstance( "X.509" );
|
|
|
|
+ try ( ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( StringUtil.base64Decode( strValue ) ) )
|
|
|
|
+ {
|
|
|
|
+ return ( X509Certificate ) certificateFactory.generateCertificate( byteArrayInputStream );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ catch ( final Exception e )
|
|
|
|
+ {
|
|
|
|
+ throw new IOException( "unable to parse x509certificate: " + e.getMessage() );
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- catch ( final PwmUnrecoverableException e )
|
|
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void toJson( final JsonWriter writer, @Nullable final X509Certificate value ) throws IOException
|
|
{
|
|
{
|
|
- final String errorMsg = "error while deserializing password data: " + e.getMessage();
|
|
|
|
- LOGGER.error( () -> errorMsg );
|
|
|
|
- throw new JsonParseException( errorMsg, e );
|
|
|
|
|
|
+ if ( value == null )
|
|
|
|
+ {
|
|
|
|
+ writer.nullValue();
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ final byte[] encoded = value.getEncoded();
|
|
|
|
+ writer.value( StringUtil.stripAllWhitespace( StringUtil.base64Encode( encoded ) ) );
|
|
|
|
+ }
|
|
|
|
+ catch ( final PwmInternalException | CertificateEncodingException e )
|
|
|
|
+ {
|
|
|
|
+ throw new IOException( "unable to json-encode certificate: " + e.getMessage() );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
|
- public JsonElement serialize( final PasswordData src, final Type typeOfSrc, final JsonSerializationContext context )
|
|
|
|
|
|
+
|
|
|
|
+ private static class PasswordDataAdapter extends JsonAdapter<PasswordData>
|
|
{
|
|
{
|
|
- try
|
|
|
|
|
|
+ @Nullable
|
|
|
|
+ @Override
|
|
|
|
+ public PasswordData fromJson( final JsonReader reader ) throws IOException
|
|
{
|
|
{
|
|
- return new JsonPrimitive( src.getStringValue() );
|
|
|
|
|
|
+ final String strValue = reader.nextString();
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ return new PasswordData( strValue );
|
|
|
|
+ }
|
|
|
|
+ catch ( final PwmUnrecoverableException e )
|
|
|
|
+ {
|
|
|
|
+ final String errorMsg = "error while deserializing password data: " + e.getMessage();
|
|
|
|
+ LOGGER.error( () -> errorMsg );
|
|
|
|
+ throw new IOException( errorMsg, e );
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- catch ( final PwmUnrecoverableException e )
|
|
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void toJson( final JsonWriter writer, @Nullable final PasswordData value ) throws IOException
|
|
{
|
|
{
|
|
- final String errorMsg = "error while serializing password data: " + e.getMessage();
|
|
|
|
- LOGGER.error( () -> errorMsg );
|
|
|
|
- throw new JsonParseException( errorMsg, e );
|
|
|
|
|
|
+ Objects.requireNonNull( value );
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ writer.value( value.getStringValue() );
|
|
|
|
+ }
|
|
|
|
+ catch ( final PwmUnrecoverableException e )
|
|
|
|
+ {
|
|
|
|
+ final String errorMsg = "error while serializing password data: " + e.getMessage();
|
|
|
|
+ LOGGER.error( () -> errorMsg );
|
|
|
|
+ throw new IOException( errorMsg, e );
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private static class LongAdderTypeAdaptor extends JsonAdapter<LongAdder>
|
|
|
|
+ {
|
|
|
|
+ @Nullable
|
|
|
|
+ @Override
|
|
|
|
+ public LongAdder fromJson( final JsonReader reader ) throws IOException
|
|
|
|
+ {
|
|
|
|
+ final String strValue = reader.nextString();
|
|
|
|
+ final long longValue = Long.parseLong( strValue );
|
|
|
|
+ final LongAdder longAddr = new LongAdder();
|
|
|
|
+ longAddr.add( longValue );
|
|
|
|
+ return longAddr;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void toJson( final JsonWriter writer, @Nullable final LongAdder value ) throws IOException
|
|
|
|
+ {
|
|
|
|
+ Objects.requireNonNull( value );
|
|
|
|
+ writer.value( value.longValue() );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- private static class PwmLdapVendorTypeAdaptor implements JsonSerializer<PwmLdapVendor>, JsonDeserializer<PwmLdapVendor>
|
|
|
|
|
|
+ private static class GsonPwmJsonServiceProvider implements PwmJsonServiceProvider
|
|
{
|
|
{
|
|
|
|
+ private static final Gson GENERIC_GSON = registerTypeAdapters( new GsonBuilder() )
|
|
|
|
+ .disableHtmlEscaping()
|
|
|
|
+ .create();
|
|
|
|
+
|
|
|
|
+ private static Gson getGson( final Flag... flags )
|
|
|
|
+ {
|
|
|
|
+ if ( flags == null || flags.length == 0 )
|
|
|
|
+ {
|
|
|
|
+ return GENERIC_GSON;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ final GsonBuilder gsonBuilder = registerTypeAdapters( new GsonBuilder() );
|
|
|
|
+
|
|
|
|
+ if ( JavaHelper.enumArrayContainsValue( flags, Flag.PrettyPrint ) )
|
|
|
|
+ {
|
|
|
|
+ gsonBuilder.setPrettyPrinting();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return gsonBuilder.create();
|
|
|
|
+ }
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
- public PwmLdapVendor deserialize( final JsonElement json, final Type typeOfT, final JsonDeserializationContext context ) throws JsonParseException
|
|
|
|
|
|
+ public <T> T deserialize( final String jsonString, final TypeToken typeToken )
|
|
{
|
|
{
|
|
- return PwmLdapVendor.fromString( json.getAsString() );
|
|
|
|
|
|
+ return getGson().fromJson( jsonString, typeToken.getType() );
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
- public JsonElement serialize( final PwmLdapVendor src, final Type typeOfSrc, final JsonSerializationContext context )
|
|
|
|
|
|
+ public <T> T deserialize( final String jsonString, final Type type )
|
|
{
|
|
{
|
|
- return new JsonPrimitive( src.name() );
|
|
|
|
|
|
+ return getGson().fromJson( jsonString, type );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public <T> T deserialize( final String json, final Class<T> classOfT, final Type... parameterizedTypes )
|
|
|
|
+ {
|
|
|
|
+ final TypeToken typeToken = TypeToken.getParameterized( classOfT, parameterizedTypes );
|
|
|
|
+ return getGson().fromJson( json, typeToken.getType() );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public <T> T deserialize( final String json, final Class<T> classOfT )
|
|
|
|
+ {
|
|
|
|
+ return getGson().fromJson( json, classOfT );
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- private static class DomainIDTypeAdaptor implements JsonSerializer<DomainID>, JsonDeserializer<DomainID>
|
|
|
|
- {
|
|
|
|
@Override
|
|
@Override
|
|
- public DomainID deserialize( final JsonElement json, final Type typeOfT, final JsonDeserializationContext context ) throws JsonParseException
|
|
|
|
|
|
+ public Map<String, String> deserializeStringMap( final String jsonString )
|
|
{
|
|
{
|
|
- final String sValue = json.getAsString();
|
|
|
|
- if ( DomainID.systemId().toString().equals( sValue ) )
|
|
|
|
|
|
+ return Map.copyOf( getGson().fromJson( jsonString, new TypeToken<Map<String, String>>()
|
|
{
|
|
{
|
|
- return DomainID.systemId();
|
|
|
|
- }
|
|
|
|
- return DomainID.create( json.getAsString() );
|
|
|
|
|
|
+ }.getType() ) );
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
- public JsonElement serialize( final DomainID src, final Type typeOfSrc, final JsonSerializationContext context )
|
|
|
|
|
|
+ public Map<String, Object> deserializeStringObjectMap( final String jsonString )
|
|
{
|
|
{
|
|
- return new JsonPrimitive( src.toString() );
|
|
|
|
|
|
+ return getGson().fromJson( jsonString, new TypeToken<Map<String, Object>>()
|
|
|
|
+ {
|
|
|
|
+ }.getType() );
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- private static class LongAdderTypeAdaptor implements JsonSerializer<LongAdder>, JsonDeserializer<LongAdder>
|
|
|
|
- {
|
|
|
|
@Override
|
|
@Override
|
|
- public LongAdder deserialize( final JsonElement json, final Type typeOfT, final JsonDeserializationContext context ) throws JsonParseException
|
|
|
|
|
|
+ public Map<String, Object> deserializeMap( final String jsonString )
|
|
{
|
|
{
|
|
- final long longValue = json.getAsLong();
|
|
|
|
- final LongAdder longAddr = new LongAdder();
|
|
|
|
- longAddr.add( longValue );
|
|
|
|
- return longAddr;
|
|
|
|
|
|
+ return Map.copyOf( getGson().fromJson( jsonString, new TypeToken<Map<String, Object>>()
|
|
|
|
+ {
|
|
|
|
+ }.getType() ) );
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
- public JsonElement serialize( final LongAdder src, final Type typeOfSrc, final JsonSerializationContext context )
|
|
|
|
|
|
+ public String serialize( final Serializable object, final Flag... flags )
|
|
{
|
|
{
|
|
- return new JsonPrimitive( src.longValue() );
|
|
|
|
|
|
+ return getGson( flags ).toJson( object );
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- private static GsonBuilder registerTypeAdapters( final GsonBuilder gsonBuilder )
|
|
|
|
- {
|
|
|
|
- gsonBuilder.registerTypeAdapter( Date.class, new DateTypeAdapter() );
|
|
|
|
- gsonBuilder.registerTypeAdapter( Instant.class, new InstantTypeAdapter() );
|
|
|
|
- gsonBuilder.registerTypeAdapter( X509Certificate.class, new X509CertificateAdapter() );
|
|
|
|
- gsonBuilder.registerTypeAdapter( byte[].class, new ByteArrayToBase64TypeAdapter() );
|
|
|
|
- gsonBuilder.registerTypeAdapter( PasswordData.class, new PasswordDataTypeAdapter() );
|
|
|
|
- gsonBuilder.registerTypeAdapter( PwmLdapVendorTypeAdaptor.class, new PwmLdapVendorTypeAdaptor() );
|
|
|
|
- gsonBuilder.registerTypeAdapter( DomainID.class, new DomainIDTypeAdaptor() );
|
|
|
|
- gsonBuilder.registerTypeAdapter( LongAdder.class, new LongAdderTypeAdaptor() );
|
|
|
|
- return gsonBuilder;
|
|
|
|
- }
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public String serializeMap( final Map object, final Flag... flags )
|
|
|
|
+ {
|
|
|
|
+ return getGson( flags ).toJson( object );
|
|
|
|
+ }
|
|
|
|
|
|
- public static <T> T cloneUsingJson( final Serializable srcObject, final Class<T> classOfT )
|
|
|
|
- {
|
|
|
|
- final String asJson = JsonUtil.serialize( srcObject );
|
|
|
|
- return JsonUtil.deserialize( asJson, classOfT );
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public String serializeCollection( final Collection object, final Flag... flags )
|
|
|
|
+ {
|
|
|
|
+ return getGson( flags ).toJson( object );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public List<String> deserializeStringList( final String jsonString )
|
|
|
|
+ {
|
|
|
|
+ return List.copyOf( getGson().fromJson( jsonString, new TypeToken<List<Object>>()
|
|
|
|
+ {
|
|
|
|
+ }.getType() ) );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public <T> T cloneUsingJson( final Serializable srcObject, final Class<T> classOfT )
|
|
|
|
+ {
|
|
|
|
+ final String asJson = JsonUtil.serialize( srcObject );
|
|
|
|
+ return JsonUtil.deserialize( asJson, classOfT );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Gson Serializer for {@link java.security.cert.X509Certificate}. Necessary because sometimes X509Certs have circular references
|
|
|
|
+ * and the default gson serializer will cause a {@code java.lang.StackOverflowError}. Standard Base64 encoding of
|
|
|
|
+ * the cert is used as the json format.
|
|
|
|
+ */
|
|
|
|
+ private static class X509CertificateAdapter implements JsonSerializer<X509Certificate>, JsonDeserializer<X509Certificate>
|
|
|
|
+ {
|
|
|
|
+ private X509CertificateAdapter( )
|
|
|
|
+ {
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public JsonElement serialize( final X509Certificate cert, final Type type, final JsonSerializationContext jsonSerializationContext )
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ return new JsonPrimitive( StringUtil.stripAllWhitespace( StringUtil.base64Encode( cert.getEncoded() ) ) );
|
|
|
|
+ }
|
|
|
|
+ catch ( final PwmInternalException | CertificateEncodingException e )
|
|
|
|
+ {
|
|
|
|
+ throw new IllegalStateException( "unable to json-encode certificate: " + e.getMessage() );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public X509Certificate deserialize( final JsonElement jsonElement, final Type type, final JsonDeserializationContext jsonDeserializationContext )
|
|
|
|
+ throws JsonParseException
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ final CertificateFactory certificateFactory = CertificateFactory.getInstance( "X.509" );
|
|
|
|
+ return ( X509Certificate ) certificateFactory.generateCertificate( new ByteArrayInputStream( StringUtil.base64Decode(
|
|
|
|
+ jsonElement.getAsString() ) ) );
|
|
|
|
+ }
|
|
|
|
+ catch ( final Exception e )
|
|
|
|
+ {
|
|
|
|
+ throw new JsonParseException( "unable to parse x509certificate: " + e.getMessage() );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * GsonSerializer that stores dates in ISO 8601 format, with a deserializer that also reads local-platform format reading.
|
|
|
|
+ */
|
|
|
|
+ private static class DateTypeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date>
|
|
|
|
+ {
|
|
|
|
+ private static final PwmDateFormat ISO_DATE_FORMAT = PwmDateFormat.newPwmDateFormat(
|
|
|
|
+ "yyyy-MM-dd'T'HH:mm:ss'Z'",
|
|
|
|
+ PwmConstants.DEFAULT_LOCALE,
|
|
|
|
+ TimeZone.getTimeZone( "Zulu" ) );
|
|
|
|
+
|
|
|
|
+ private DateFormat getGsonDateFormat()
|
|
|
|
+ {
|
|
|
|
+ final DateFormat gsonDateFormat = DateFormat.getDateTimeInstance( DateFormat.DEFAULT, DateFormat.DEFAULT );
|
|
|
|
+ gsonDateFormat.setTimeZone( TimeZone.getDefault() );
|
|
|
|
+ return gsonDateFormat;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private DateTypeAdapter( )
|
|
|
|
+ {
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public JsonElement serialize( final Date date, final Type type, final JsonSerializationContext jsonSerializationContext )
|
|
|
|
+ {
|
|
|
|
+ return new JsonPrimitive( ISO_DATE_FORMAT.format( date.toInstant() ) );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public Date deserialize( final JsonElement jsonElement, final Type type, final JsonDeserializationContext jsonDeserializationContext )
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ return Date.from( ISO_DATE_FORMAT.parse( jsonElement.getAsString() ) );
|
|
|
|
+ }
|
|
|
|
+ catch ( final ParseException e )
|
|
|
|
+ {
|
|
|
|
+ /* noop */
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // for backwards compatibility
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ return getGsonDateFormat().parse( jsonElement.getAsString() );
|
|
|
|
+ }
|
|
|
|
+ catch ( final ParseException e )
|
|
|
|
+ {
|
|
|
|
+ LOGGER.debug( () -> "unable to parse stored json Date.class timestamp '" + jsonElement.getAsString() + "' error: " + e.getMessage() );
|
|
|
|
+ throw new JsonParseException( e );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * GsonSerializer that stores instants in ISO 8601 format, with a deserializer that also reads local-platform format reading.
|
|
|
|
+ */
|
|
|
|
+ private static class InstantTypeAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant>
|
|
|
|
+ {
|
|
|
|
+ private InstantTypeAdapter( )
|
|
|
|
+ {
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public JsonElement serialize( final Instant instant, final Type type, final JsonSerializationContext jsonSerializationContext )
|
|
|
|
+ {
|
|
|
|
+ return new JsonPrimitive( JavaHelper.toIsoDate( instant ) );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public Instant deserialize( final JsonElement jsonElement, final Type type, final JsonDeserializationContext jsonDeserializationContext )
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ return JavaHelper.parseIsoToInstant( jsonElement.getAsString() );
|
|
|
|
+ }
|
|
|
|
+ catch ( final Exception e )
|
|
|
|
+ {
|
|
|
|
+ LOGGER.debug( () -> "unable to parse stored json Instant.class timestamp '" + jsonElement.getAsString() + "' error: " + e.getMessage() );
|
|
|
|
+ throw new JsonParseException( e );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static class PasswordDataTypeAdapter implements JsonSerializer<PasswordData>, JsonDeserializer<PasswordData>
|
|
|
|
+ {
|
|
|
|
+ @Override
|
|
|
|
+ public PasswordData deserialize( final JsonElement json, final Type typeOfT, final JsonDeserializationContext context ) throws JsonParseException
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ return new PasswordData( json.getAsString() );
|
|
|
|
+ }
|
|
|
|
+ catch ( final PwmUnrecoverableException e )
|
|
|
|
+ {
|
|
|
|
+ final String errorMsg = "error while deserializing password data: " + e.getMessage();
|
|
|
|
+ LOGGER.error( () -> errorMsg );
|
|
|
|
+ throw new JsonParseException( errorMsg, e );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public JsonElement serialize( final PasswordData src, final Type typeOfSrc, final JsonSerializationContext context )
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ return new JsonPrimitive( src.getStringValue() );
|
|
|
|
+ }
|
|
|
|
+ catch ( final PwmUnrecoverableException e )
|
|
|
|
+ {
|
|
|
|
+ final String errorMsg = "error while serializing password data: " + e.getMessage();
|
|
|
|
+ LOGGER.error( () -> errorMsg );
|
|
|
|
+ throw new JsonParseException( errorMsg, e );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static class PwmLdapVendorTypeAdaptor implements JsonSerializer<PwmLdapVendor>, JsonDeserializer<PwmLdapVendor>
|
|
|
|
+ {
|
|
|
|
+ @Override
|
|
|
|
+ public PwmLdapVendor deserialize( final JsonElement json, final Type typeOfT, final JsonDeserializationContext context ) throws JsonParseException
|
|
|
|
+ {
|
|
|
|
+ return PwmLdapVendor.fromString( json.getAsString() );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public JsonElement serialize( final PwmLdapVendor src, final Type typeOfSrc, final JsonSerializationContext context )
|
|
|
|
+ {
|
|
|
|
+ return new JsonPrimitive( src.name() );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static class DomainIDTypeAdaptor implements JsonSerializer<DomainID>, JsonDeserializer<DomainID>
|
|
|
|
+ {
|
|
|
|
+ @Override
|
|
|
|
+ public DomainID deserialize( final JsonElement json, final Type typeOfT, final JsonDeserializationContext context ) throws JsonParseException
|
|
|
|
+ {
|
|
|
|
+ final String sValue = json.getAsString();
|
|
|
|
+ if ( DomainID.systemId().toString().equals( sValue ) )
|
|
|
|
+ {
|
|
|
|
+ return DomainID.systemId();
|
|
|
|
+ }
|
|
|
|
+ return DomainID.create( json.getAsString() );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public JsonElement serialize( final DomainID src, final Type typeOfSrc, final JsonSerializationContext context )
|
|
|
|
+ {
|
|
|
|
+ return new JsonPrimitive( src.toString() );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static class LongAdderTypeAdaptor implements JsonSerializer<LongAdder>, JsonDeserializer<LongAdder>
|
|
|
|
+ {
|
|
|
|
+ @Override
|
|
|
|
+ public LongAdder deserialize( final JsonElement json, final Type typeOfT, final JsonDeserializationContext context ) throws JsonParseException
|
|
|
|
+ {
|
|
|
|
+ final long longValue = json.getAsLong();
|
|
|
|
+ final LongAdder longAddr = new LongAdder();
|
|
|
|
+ longAddr.add( longValue );
|
|
|
|
+ return longAddr;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public JsonElement serialize( final LongAdder src, final Type typeOfSrc, final JsonSerializationContext context )
|
|
|
|
+ {
|
|
|
|
+ return new JsonPrimitive( src.longValue() );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static GsonBuilder registerTypeAdapters( final GsonBuilder gsonBuilder )
|
|
|
|
+ {
|
|
|
|
+ gsonBuilder.registerTypeAdapter( Date.class, new DateTypeAdapter() );
|
|
|
|
+ gsonBuilder.registerTypeAdapter( Instant.class, new InstantTypeAdapter() );
|
|
|
|
+ gsonBuilder.registerTypeAdapter( X509Certificate.class, new X509CertificateAdapter() );
|
|
|
|
+ gsonBuilder.registerTypeAdapter( PasswordData.class, new PasswordDataTypeAdapter() );
|
|
|
|
+ gsonBuilder.registerTypeAdapter( PwmLdapVendorTypeAdaptor.class, new PwmLdapVendorTypeAdaptor() );
|
|
|
|
+ gsonBuilder.registerTypeAdapter( DomainID.class, new DomainIDTypeAdaptor() );
|
|
|
|
+ gsonBuilder.registerTypeAdapter( LongAdder.class, new LongAdderTypeAdaptor() );
|
|
|
|
+ return gsonBuilder;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|