Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow external(custom) CodecRegistry for serialization and querying i…
…n mongo adapter. This change will allow users to use other serialization frameworks than Gson (eg. jackson or native BSON PojoCodec(s) etc.). Backwards compatibility with Gson is preserved. 1. Moved bson/gson adapters to a separate package `bson4gson` which can be developed independently. 2. Changed RepositorySetup.Builder to accept CodecRegistry (if not set, defaults to Gson delegate). 3. Upgraded Gson library to 2.8.5 (from 2.8.0) because of new method `gson.newBuilder()` which allows registering new TypeAdapterFactories with existing gson instance.
- Loading branch information
1 parent
0be8792
commit 37b43b5
Showing
24 changed files
with
750 additions
and
313 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
/* | ||
Copyright 2013-2015 Immutables Authors and Contributors | ||
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.immutables.mongo.bson4gson; | ||
|
||
import com.google.common.base.Preconditions; | ||
import com.google.gson.Gson; | ||
import com.google.gson.TypeAdapter; | ||
import com.google.gson.TypeAdapterFactory; | ||
import com.google.gson.reflect.TypeToken; | ||
import com.google.gson.stream.JsonReader; | ||
import com.google.gson.stream.JsonWriter; | ||
import org.bson.AbstractBsonReader; | ||
import org.bson.codecs.Codec; | ||
import org.bson.codecs.DecoderContext; | ||
import org.bson.codecs.EncoderContext; | ||
import org.bson.codecs.configuration.CodecConfigurationException; | ||
import org.bson.codecs.configuration.CodecRegistry; | ||
|
||
import java.io.IOException; | ||
|
||
/** | ||
* Set of utilities to bridge <a href="http://bsonspec.org/">BSON</a> and | ||
* <a href="https://github.com/google/gson">Gson</a> standard classes like | ||
* {@link TypeAdapter} / {@link Codec}(s). | ||
*/ | ||
public final class Codecs { | ||
|
||
private Codecs() { | ||
} | ||
|
||
/** | ||
* Build a TypeAdapter from {@link Codec} opposite of {@link #codecFromTypeAdapter(Class, TypeAdapter)}. | ||
* | ||
* @param codec existing codec | ||
* @return type adapter which delegates calls to a codec. | ||
*/ | ||
public static <T> TypeAdapter<T> typeAdapterFromCodec(final Codec<T> codec) { | ||
Preconditions.checkNotNull(codec, "codec"); | ||
return new TypeAdapter<T>() { | ||
@Override | ||
public void write(JsonWriter out, T value) throws IOException { | ||
BsonWriter writer = (BsonWriter) out; | ||
org.bson.BsonWriter delegate = writer.unwrap(); | ||
codec.encode(delegate, value, EncoderContext.builder().build()); | ||
} | ||
|
||
@Override | ||
public T read(JsonReader in) throws IOException { | ||
BsonReader reader = (BsonReader) in; | ||
org.bson.BsonReader delegate = reader.unwrap(); | ||
return codec.decode(delegate, DecoderContext.builder().build()); | ||
} | ||
}; | ||
} | ||
|
||
/** | ||
* Gson Factory which gives preference to existing adapters from {@code gson} instance. However, | ||
* if type is not supported it will query {@link CodecRegistry} to create one (if possible). | ||
* | ||
* <p>This allows supporting Bson types by Gson natively (eg. for {@link org.bson.types.ObjectId}). | ||
* | ||
* @param registry existing registry which will be used if type is unknown to {@code gson}. | ||
* @return factory which delegates to {@code registry} for unknown types. | ||
*/ | ||
public static TypeAdapterFactory delegatingTypeAdapterFactory(final CodecRegistry registry) { | ||
Preconditions.checkNotNull(registry, "registry"); | ||
return new TypeAdapterFactory() { | ||
@Override | ||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { | ||
boolean hasAdapter; | ||
try { | ||
TypeAdapter<T> adapter = gson.getDelegateAdapter(this, type); | ||
hasAdapter = !isReflectiveTypeAdapter(adapter); | ||
} catch (IllegalArgumentException e) { | ||
hasAdapter = false; | ||
} | ||
|
||
if (hasAdapter) { | ||
return null; | ||
} | ||
|
||
try { | ||
@SuppressWarnings("unchecked") | ||
Codec<T> codec = (Codec<T>) registry.get(type.getRawType()); | ||
return typeAdapterFromCodec(codec); | ||
} catch (CodecConfigurationException e1) { | ||
return null; | ||
} | ||
|
||
} | ||
}; | ||
} | ||
|
||
/** | ||
* Build a codec from {@link TypeAdapter}. Opposite of {@link #typeAdapterFromCodec(Codec)}. | ||
* | ||
* @param type type handled by this adapter | ||
* @param adapter existing adapter | ||
* @param <T> codec value type | ||
* @throws CodecConfigurationException if adapter is not supported | ||
* @return new instance of the codec which handles {@code type}. | ||
*/ | ||
public static <T> Codec<T> codecFromTypeAdapter(Class<T> type, TypeAdapter<T> adapter) { | ||
if (isReflectiveTypeAdapter(adapter)) { | ||
throw new CodecConfigurationException(String.format("%s can't be build from %s " + | ||
"(for type %s)", TypeAdapterCodec.class.getSimpleName(), | ||
adapter.getClass().getName(), type.getName())); | ||
} | ||
return new TypeAdapterCodec<>(type, adapter); | ||
} | ||
|
||
/** | ||
* Given existing {@code Gson} instance builds a {@link CodecRegistry}. | ||
* | ||
* @param gson preconfigured instance | ||
* @return wrapper for {@code gson}. | ||
*/ | ||
public static CodecRegistry codecRegistryFromGson(final Gson gson) { | ||
Preconditions.checkNotNull(gson, "gson"); | ||
return new CodecRegistry() { | ||
@Override | ||
public <T> Codec<T> get(Class<T> clazz) { | ||
return codecFromTypeAdapter(clazz, gson.getAdapter(clazz)); | ||
} | ||
}; | ||
} | ||
|
||
static <A> boolean isReflectiveTypeAdapter(TypeAdapter<A> adapter) { | ||
Preconditions.checkNotNull(adapter, "adapter"); | ||
return adapter instanceof com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.Adapter; | ||
} | ||
|
||
/** | ||
* Codec which delegates all calls to existing type adapter. | ||
* | ||
* @param <T> type handled by this codec | ||
*/ | ||
private static class TypeAdapterCodec<T> implements Codec<T> { | ||
private final Class<T> clazz; | ||
private final TypeAdapter<T> adapter; | ||
|
||
private TypeAdapterCodec(Class<T> type, TypeAdapter<T> adapter) { | ||
this.clazz = Preconditions.checkNotNull(type, "type"); | ||
Preconditions.checkArgument(!isReflectiveTypeAdapter(adapter), | ||
"Type adapter %s for type '%s' is not supported." | ||
+ " This may happen when using default RepositorySetup.forUri and" | ||
+ " META-INF/services/..TypeAdapterFactory files are not compiled or accessible." | ||
+ " Alternatively this may happen if creating custom RepositorySetup with Gson instance," | ||
+ " which does not have type adapters registered.", adapter.getClass().getName(), type); | ||
this.adapter = adapter; | ||
} | ||
|
||
@Override | ||
public T decode(org.bson.BsonReader reader, DecoderContext decoderContext) { | ||
if (!(reader instanceof AbstractBsonReader)) { | ||
throw new UnsupportedOperationException(String.format("Only readers of type %s supported. Yours is %s", | ||
AbstractBsonReader.class.getName(), reader.getClass().getName())); | ||
} | ||
|
||
try { | ||
return adapter.read(new BsonReader((AbstractBsonReader) reader)); | ||
} catch (IOException e) { | ||
throw new RuntimeException(String.format("Couldn't read %s", clazz), e); | ||
} | ||
} | ||
|
||
@Override | ||
public void encode(org.bson.BsonWriter writer, T value, EncoderContext encoderContext) { | ||
try { | ||
adapter.write(new BsonWriter(writer), value); | ||
} catch (IOException e) { | ||
throw new RuntimeException(String.format("Couldn't write value of class %s: %s", clazz.getName(), value), e); | ||
} | ||
} | ||
|
||
@Override | ||
public Class<T> getEncoderClass() { | ||
return clazz; | ||
} | ||
|
||
} | ||
} |
Oops, something went wrong.