/*
* Copyright 2008-present MongoDB, 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.bson.codecs.pojo;
import org.bson.codecs.Codec;
import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.diagnostics.Logger;
import org.bson.diagnostics.Loggers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static org.bson.assertions.Assertions.notNull;
Provides Codecs for registered POJOs via the ClassModel abstractions.
Since: 3.5
/**
* Provides Codecs for registered POJOs via the ClassModel abstractions.
*
* @since 3.5
*/
public final class PojoCodecProvider implements CodecProvider {
static final Logger LOGGER = Loggers.getLogger("codecs.pojo");
private final boolean automatic;
private final Map<Class<?>, ClassModel<?>> classModels;
private final Set<String> packages;
private final List<Convention> conventions;
private final DiscriminatorLookup discriminatorLookup;
private final List<PropertyCodecProvider> propertyCodecProviders;
private PojoCodecProvider(final boolean automatic, final Map<Class<?>, ClassModel<?>> classModels, final Set<String> packages,
final List<Convention> conventions, final List<PropertyCodecProvider> propertyCodecProviders) {
this.automatic = automatic;
this.classModels = classModels;
this.packages = packages;
this.conventions = conventions;
this.discriminatorLookup = new DiscriminatorLookup(classModels, packages);
this.propertyCodecProviders = propertyCodecProviders;
}
Creates a Builder so classes or packages can be registered and configured before creating an immutable CodecProvider.
See Also: Returns: the Builder
/**
* Creates a Builder so classes or packages can be registered and configured before creating an immutable CodecProvider.
*
* @return the Builder
* @see Builder#register(Class[])
*/
public static Builder builder() {
return new Builder();
}
@Override
public <T> Codec<T> get(final Class<T> clazz, final CodecRegistry registry) {
return getPojoCodec(clazz, registry);
}
@SuppressWarnings("unchecked")
private <T> PojoCodec<T> getPojoCodec(final Class<T> clazz, final CodecRegistry registry) {
ClassModel<T> classModel = (ClassModel<T>) classModels.get(clazz);
if (classModel != null) {
return new PojoCodecImpl<T>(classModel, registry, propertyCodecProviders, discriminatorLookup);
} else if (automatic || (clazz.getPackage() != null && packages.contains(clazz.getPackage().getName()))) {
try {
classModel = createClassModel(clazz, conventions);
if (clazz.isInterface() || !classModel.getPropertyModels().isEmpty()) {
discriminatorLookup.addClassModel(classModel);
return new AutomaticPojoCodec<T>(new PojoCodecImpl<T>(classModel, registry, propertyCodecProviders,
discriminatorLookup));
}
} catch (Exception e) {
LOGGER.warn(format("Cannot use '%s' with the PojoCodec.", clazz.getSimpleName()), e);
return null;
}
}
return null;
}
A Builder for the PojoCodecProvider
/**
* A Builder for the PojoCodecProvider
*/
public static final class Builder {
private final Set<String> packages = new HashSet<String>();
private final Map<Class<?>, ClassModel<?>> classModels = new HashMap<Class<?>, ClassModel<?>>();
private final List<Class<?>> clazzes = new ArrayList<Class<?>>();
private List<Convention> conventions = null;
private final List<PropertyCodecProvider> propertyCodecProviders = new ArrayList<PropertyCodecProvider>();
private boolean automatic;
Creates the PojoCodecProvider with the classes or packages that configured and registered.
See Also: Returns: the Provider
/**
* Creates the PojoCodecProvider with the classes or packages that configured and registered.
*
* @return the Provider
* @see #register(Class...)
*/
public PojoCodecProvider build() {
List<Convention> immutableConventions = conventions != null
? Collections.unmodifiableList(new ArrayList<Convention>(conventions))
: null;
for (Class<?> clazz : clazzes) {
if (!classModels.containsKey(clazz)) {
register(createClassModel(clazz, immutableConventions));
}
}
return new PojoCodecProvider(automatic, classModels, packages, immutableConventions, propertyCodecProviders);
}
Sets whether the provider should automatically try to wrap a ClassModel
for any class that is requested. Note: As Java Beans are convention based, when using automatic settings the provider should be the last provider in the
registry.
Params: - automatic – whether to automatically wrap
ClassModels
or not.
Returns: this
/**
* Sets whether the provider should automatically try to wrap a {@link ClassModel} for any class that is requested.
*
* <p>Note: As Java Beans are convention based, when using automatic settings the provider should be the last provider in the
* registry.</p>
*
* @param automatic whether to automatically wrap {@code ClassModels} or not.
* @return this
*/
public Builder automatic(final boolean automatic) {
this.automatic = automatic;
return this;
}
Sets the conventions to use when creating ClassModels
from classes or packages. Params: - conventions – a list of conventions
Returns: this
/**
* Sets the conventions to use when creating {@code ClassModels} from classes or packages.
*
* @param conventions a list of conventions
* @return this
*/
public Builder conventions(final List<Convention> conventions) {
this.conventions = notNull("conventions", conventions);
return this;
}
Registers a classes with the builder for inclusion in the Provider.
Note: Uses reflection for the property mapping. If no conventions are configured on the builder the Conventions.DEFAULT_CONVENTIONS
will be used.
Params: - classes – the classes to register
Returns: this
/**
* Registers a classes with the builder for inclusion in the Provider.
*
* <p>Note: Uses reflection for the property mapping. If no conventions are configured on the builder the
* {@link Conventions#DEFAULT_CONVENTIONS} will be used.</p>
*
* @param classes the classes to register
* @return this
*/
public Builder register(final Class<?>... classes) {
clazzes.addAll(asList(classes));
return this;
}
Registers classModels for inclusion in the Provider.
Params: - classModels – the classModels to register
Returns: this
/**
* Registers classModels for inclusion in the Provider.
*
* @param classModels the classModels to register
* @return this
*/
public Builder register(final ClassModel<?>... classModels) {
notNull("classModels", classModels);
for (ClassModel<?> classModel : classModels) {
this.classModels.put(classModel.getType(), classModel);
}
return this;
}
Registers the packages of the given classes with the builder for inclusion in the Provider. This will allow classes in the
given packages to mapped for use with PojoCodecProvider.
Note: Uses reflection for the field mapping. If no conventions are configured on the builder the Conventions.DEFAULT_CONVENTIONS
will be used.
Params: - packageNames – the package names to register
Returns: this
/**
* Registers the packages of the given classes with the builder for inclusion in the Provider. This will allow classes in the
* given packages to mapped for use with PojoCodecProvider.
*
* <p>Note: Uses reflection for the field mapping. If no conventions are configured on the builder the
* {@link Conventions#DEFAULT_CONVENTIONS} will be used.</p>
*
* @param packageNames the package names to register
* @return this
*/
public Builder register(final String... packageNames) {
packages.addAll(asList(notNull("packageNames", packageNames)));
return this;
}
Registers codec providers that receive the type parameters of properties for instances encoded and decoded by a PojoCodec
handled by this provider. Note that you should prefer working with the CodecRegistry
/CodecProvider
hierarchy. Providers should only be registered here if a codec needs to be created for custom container types like optionals and collections. Support for types Map
and Collection
are built-in so explicitly handling them is not necessary.
Params: - providers – property codec providers to register
Returns: this Since: 3.6
/**
* Registers codec providers that receive the type parameters of properties for instances encoded and decoded
* by a {@link PojoCodec} handled by this provider.
*
* <p>Note that you should prefer working with the {@link CodecRegistry}/{@link CodecProvider} hierarchy. Providers
* should only be registered here if a codec needs to be created for custom container types like optionals and
* collections. Support for types {@link Map} and {@link java.util.Collection} are built-in so explicitly handling
* them is not necessary.
* @param providers property codec providers to register
* @return this
* @since 3.6
*/
public Builder register(final PropertyCodecProvider... providers) {
propertyCodecProviders.addAll(asList(notNull("providers", providers)));
return this;
}
private Builder() {
}
}
private static <T> ClassModel<T> createClassModel(final Class<T> clazz, final List<Convention> conventions) {
ClassModelBuilder<T> builder = ClassModel.builder(clazz);
if (conventions != null) {
builder.conventions(conventions);
}
return builder.build();
}
}