blob: c5b678d266915cd2acd1179a2786137a6b186dfc [file] [log] [blame]
/*
* Copyright (C) 2012-2015 DataStax 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 com.datastax.driver.mapping;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.datastax.driver.core.ProtocolVersion;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.TypeCodec;
import com.datastax.driver.mapping.annotations.Accessor;
import com.datastax.driver.mapping.annotations.Table;
import com.datastax.driver.mapping.annotations.UDT;
/**
* Mapping manager from which to obtain entity mappers.
*/
public class MappingManager {
private final Session session;
final boolean isCassandraV1;
private volatile Map<Class<?>, Mapper<?>> mappers = Collections.emptyMap();
private volatile Map<Class<?>, MappedUDTCodec<?>> udtCodecs = Collections.emptyMap();
private volatile Map<Class<?>, Object> accessors = Collections.emptyMap();
/**
* Creates a new {@code MappingManager} using the provided {@code Session}.
* <p>
* Note that this constructor forces the initialization of the session (see
* {@link #MappingManager(Session, ProtocolVersion)} if that is a problem for you).
*
* @param session the {@code Session} to use.
*/
public MappingManager(Session session) {
this(session, getProtocolVersion(session));
}
private static ProtocolVersion getProtocolVersion(Session session) {
session.init();
return session.getCluster().getConfiguration().getProtocolOptions().getProtocolVersion();
}
/**
* Creates a new {@code MappingManager} using the provided {@code Session}.
* <p>
* This constructor is only provided for backward compatibility: before 2.1.7, {@code MappingManager} could be
* built from an uninitialized session; since 2.1.7, the mapper needs to know the active protocol version to
* adapt its internal requests, so {@link #MappingManager(Session)} will now initialize the session if needed.
* If you rely on the session not being initialized, use this constructor and provide the version manually.
*
* @param session the {@code Session} to use.
* @param protocolVersion the protocol version that will be used with this session.
*
* @since 2.1.7
*/
public MappingManager(Session session, ProtocolVersion protocolVersion) {
this.session = session;
// This is not strictly correct because we could connect to C* 2.0 with the v1 protocol.
// But mappers need to make a decision early so that generated queries are compatible, and we don't know in advance
// which nodes might join the cluster later.
// At least if protocol >=2 we know there won't be any 1.2 nodes ever.
this.isCassandraV1 = (protocolVersion == ProtocolVersion.V1);
}
/**
* The underlying {@code Session} used by this manager.
* <p>
* Note that you can get obtain the {@code Cluster} object corresponding
* to that session using {@code getSession().getCluster()}.
* <p>
* It is inadvisable to close the returned Session while this manager and
* its mappers are in use.
*
* @return the underlying session used by this manager.
*/
public Session getSession() {
return session;
}
/**
* Creates a {@code Mapper} for the provided class (that must be annotated by a
* {@link Table} annotation).
* <p>
* The {@code MappingManager} only ever keeps one Mapper for each class, and so calling this
* method multiple times on the same class will always return the same object.
* <p>
* If the type of any field in the class is an {@link UDT}-annotated classes, a codec for that
* class will automatically be created and registered with the underlying {@code Cluster}.
* This works recursively with UDTs nested in other UDTs or in collections.
*
* @param <T> the type of the class to map.
* @param klass the (annotated) class for which to return the mapper.
* @return the {@code Mapper} object for class {@code klass}.
*/
public <T> Mapper<T> mapper(Class<T> klass) {
return getMapper(klass);
}
/**
* Creates a {@code TypeCodec} for the provided class (that must be annotated by
* a {@link UDT} annotation).
* <p>
* This method also registers the codec against the underlying {@code Cluster}.
* In addition, the codecs for any nested UDTs will also be created and registered.
* <p>
* You don't need to call this method explicitly if you already call {@link #mapper(Class)}
* for a class that references this UDT class (creating a mapper will automatically
* process all UDTs that it uses).
*
* @param <T> the type of the class to map.
* @param klass the (annotated) class for which to return the codec.
* @return the codec that maps the provided class to the corresponding user-defined type.
*/
public <T> TypeCodec<T> udtCodec(Class<T> klass) {
return getUDTCodec(klass);
}
/**
* Creates an accessor object based on the provided interface (that must be annotated by
* a {@link Accessor} annotation).
* <p>
* The {@code MappingManager} only ever keep one Accessor for each class, and so calling this
* method multiple time on the same class will always return the same object.
*
* @param <T> the type of the accessor class.
* @param klass the (annotated) class for which to create an accessor object.
* @return the accessor object for class {@code klass}.
*/
public <T> T createAccessor(Class<T> klass) {
return getAccessor(klass);
}
@SuppressWarnings("unchecked")
private <T> Mapper<T> getMapper(Class<T> klass) {
Mapper<T> mapper = (Mapper<T>)mappers.get(klass);
if (mapper == null) {
synchronized (mappers) {
mapper = (Mapper<T>)mappers.get(klass);
if (mapper == null) {
EntityMapper<T> entityMapper = AnnotationParser.parseEntity(klass, ReflectionMapper.factory(), this);
mapper = new Mapper<T>(this, klass, entityMapper);
Map<Class<?>, Mapper<?>> newMappers = new HashMap<Class<?>, Mapper<?>>(mappers);
newMappers.put(klass, mapper);
mappers = newMappers;
}
}
}
return mapper;
}
@SuppressWarnings("unchecked")
<T> TypeCodec<T> getUDTCodec(Class<T> mappedClass) {
MappedUDTCodec<T> codec = (MappedUDTCodec<T>)udtCodecs.get(mappedClass);
if (codec == null) {
synchronized (udtCodecs) {
codec = (MappedUDTCodec<T>)udtCodecs.get(mappedClass);
if (codec == null) {
codec = AnnotationParser.parseUDT(mappedClass, ReflectionMapper.factory(), this);
session.getCluster().getConfiguration().getCodecRegistry().register(codec);
HashMap<Class<?>, MappedUDTCodec<?>> newCodecs = new HashMap<Class<?>, MappedUDTCodec<?>>(udtCodecs);
newCodecs.put(mappedClass, codec);
udtCodecs = newCodecs;
}
}
}
return codec;
}
@SuppressWarnings("unchecked")
private <T> T getAccessor(Class<T> klass) {
T accessor = (T)accessors.get(klass);
if (accessor == null) {
synchronized (accessors) {
accessor = (T)accessors.get(klass);
if (accessor == null) {
AccessorMapper<T> mapper = AnnotationParser.parseAccessor(klass, AccessorReflectionMapper.factory(), this);
mapper.prepare(this);
accessor = mapper.createProxy();
Map<Class<?>, Object> newAccessors = new HashMap<Class<?>, Object>(accessors);
newAccessors.put(klass, accessor);
accessors = newAccessors;
}
}
}
return accessor;
}
}