net.ravendb.client.documents.conventions.DocumentConventions Maven / Gradle / Ivy
package net.ravendb.client.documents.conventions;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import net.ravendb.client.Constants;
import net.ravendb.client.documents.operations.configuration.ClientConfiguration;
import net.ravendb.client.exceptions.RavenException;
import net.ravendb.client.extensions.JsonExtensions;
import net.ravendb.client.http.AggressiveCacheMode;
import net.ravendb.client.http.AggressiveCacheOptions;
import net.ravendb.client.http.ReadBalanceBehavior;
import net.ravendb.client.primitives.Reference;
import net.ravendb.client.primitives.Tuple;
import net.ravendb.client.util.Inflector;
import net.ravendb.client.util.ReflectionUtil;
import org.apache.commons.lang3.ObjectUtils;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.time.Duration;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
public class DocumentConventions {
public static final DocumentConventions defaultConventions = new DocumentConventions();
public static final DocumentConventions defaultForServerConventions = new DocumentConventions();
static {
defaultConventions.freeze();
defaultForServerConventions._sendApplicationIdentifier = false;
defaultForServerConventions.freeze();
}
private static final Map _cachedDefaultTypeCollectionNames = new HashMap<>();
private final List>> _listOfQueryValueToObjectConverters = new ArrayList<>();
private List>> _listOfRegisteredIdConventions = new ArrayList<>();
private boolean _frozen;
private ClientConfiguration _originalConfiguration;
private final Map _idPropertyCache = new HashMap<>();
private boolean _saveEnumsAsIntegers;
private String _identityPartsSeparator;
private boolean _disableTopologyUpdates;
private Function _findIdentityProperty;
private Function _transformClassCollectionNameToDocumentIdPrefix;
private BiFunction _documentIdGenerator;
private Function _findIdentityPropertyNameFromCollectionName;
private Function _findCollectionName;
private Function _findJavaClassName;
private BiFunction _findJavaClass;
private Function _findJavaClassByName;
private boolean _useOptimisticConcurrency;
private boolean _throwIfQueryPageSizeIsNotSet;
private int _maxNumberOfRequestsPerSession;
private Duration _requestTimeout;
private Duration _firstBroadcastAttemptTimeout;
private Duration _secondBroadcastAttemptTimeout;
private ReadBalanceBehavior _readBalanceBehavior;
private int _maxHttpCacheSize;
private ObjectMapper _entityMapper;
private Boolean _useCompression;
private boolean _sendApplicationIdentifier;
private AggressiveCacheConventions _aggressiveCache;
public AggressiveCacheConventions aggressiveCache() {
return _aggressiveCache;
}
public static class AggressiveCacheConventions {
private final DocumentConventions _conventions;
private final AggressiveCacheOptions _aggressiveCacheOptions;
public AggressiveCacheConventions(DocumentConventions conventions) {
_conventions = conventions;
_aggressiveCacheOptions = new AggressiveCacheOptions(Duration.ofDays(1), AggressiveCacheMode.TRACK_CHANGES);
}
public Duration getDuration() {
return _aggressiveCacheOptions.getDuration();
}
public void setDuration(Duration duration) {
_aggressiveCacheOptions.setDuration(duration);
}
public AggressiveCacheMode getMode() {
return _aggressiveCacheOptions.getMode();
}
public void setMode(AggressiveCacheMode mode) {
_aggressiveCacheOptions.setMode(mode);
}
}
public DocumentConventions() {
_readBalanceBehavior = ReadBalanceBehavior.NONE;
_findIdentityProperty = q -> q.getName().equals("id");
_identityPartsSeparator = "/";
_findIdentityPropertyNameFromCollectionName = entityName -> "Id";
_findJavaClass = (String id, ObjectNode doc) -> {
JsonNode metadata = doc.get(Constants.Documents.Metadata.KEY);
if (metadata != null) {
TextNode javaType = (TextNode) metadata.get(Constants.Documents.Metadata.RAVEN_JAVA_TYPE);
if (javaType != null) {
return javaType.asText();
}
}
return null;
};
_findJavaClassName = type -> ReflectionUtil.getFullNameWithoutVersionInformation(type);
_findJavaClassByName = name -> {
try {
return Class.forName(name);
} catch (ClassNotFoundException e) {
throw new RavenException("Unable to find class by name = " + name, e);
}
};
_transformClassCollectionNameToDocumentIdPrefix = collectionName -> defaultTransformCollectionNameToDocumentIdPrefix(collectionName);
_findCollectionName = type -> defaultGetCollectionName(type);
_maxNumberOfRequestsPerSession = 30;
_maxHttpCacheSize = 128 * 1024 * 1024;
_entityMapper = JsonExtensions.getDefaultEntityMapper();
_aggressiveCache = new AggressiveCacheConventions(this);
_firstBroadcastAttemptTimeout = Duration.ofSeconds(5);
_secondBroadcastAttemptTimeout = Duration.ofSeconds(30);
_sendApplicationIdentifier = true;
}
public boolean hasExplicitlySetCompressionUsage() {
return _useCompression != null;
}
public Duration getRequestTimeout() {
return _requestTimeout;
}
public void setRequestTimeout(Duration requestTimeout) {
assertNotFrozen();
_requestTimeout = requestTimeout;
}
/**
* Enables sending a unique application identifier to the RavenDB Server that is used for Client API usage tracking.
* It allows RavenDB Server to issue performance hint notifications e.g. during robust topology update requests which could indicate Client API misuse impacting the overall performance
* @return if option is enabled
*/
public boolean isSendApplicationIdentifier() {
return _sendApplicationIdentifier;
}
/**
* Enables sending a unique application identifier to the RavenDB Server that is used for Client API usage tracking.
* It allows RavenDB Server to issue performance hint notifications e.g. during robust topology update requests which could indicate Client API misuse impacting the overall performance
* @param sendApplicationIdentifier if option should be enabled
*/
public void setSendApplicationIdentifier(boolean sendApplicationIdentifier) {
assertNotFrozen();
_sendApplicationIdentifier = sendApplicationIdentifier;
}
/**
* Get the timeout for the second broadcast attempt.
* Default: 30 seconds
*
* Upon failure of the first attempt the request executor will resend the command to all nodes simultaneously.
* @return broadcast timeout
*/
public Duration getSecondBroadcastAttemptTimeout() {
return _secondBroadcastAttemptTimeout;
}
/**
* Set the timeout for the second broadcast attempt.
* Default: 30 seconds
*
* Upon failure of the first attempt the request executor will resend the command to all nodes simultaneously.
* @param secondBroadcastAttemptTimeout broadcast timeout
*/
public void setSecondBroadcastAttemptTimeout(Duration secondBroadcastAttemptTimeout) {
assertNotFrozen();
_secondBroadcastAttemptTimeout = secondBroadcastAttemptTimeout;
}
/**
* Get the timeout for the first broadcast attempt.
* Default: 5 seconds
*
* First attempt will send a single request to a selected node.
* @return broadcast timeout
*/
public Duration getFirstBroadcastAttemptTimeout() {
return _firstBroadcastAttemptTimeout;
}
/**
* Set the timeout for the first broadcast attempt.
* Default: 5 seconds
*
* First attempt will send a single request to a selected node.
* @param firstBroadcastAttemptTimeout broadcast timeout
*/
public void setFirstBroadcastAttemptTimeout(Duration firstBroadcastAttemptTimeout) {
assertNotFrozen();
_firstBroadcastAttemptTimeout = firstBroadcastAttemptTimeout;
}
public Boolean isUseCompression() {
if (_useCompression == null) {
return true;
}
return _useCompression;
}
public void setUseCompression(Boolean useCompression) {
assertNotFrozen();
_useCompression = useCompression;
}
public ObjectMapper getEntityMapper() {
return _entityMapper;
}
public void setEntityMapper(ObjectMapper entityMapper) {
_entityMapper = entityMapper;
}
public ReadBalanceBehavior getReadBalanceBehavior() {
return _readBalanceBehavior;
}
public void setReadBalanceBehavior(ReadBalanceBehavior _readBalanceBehavior) {
assertNotFrozen();
this._readBalanceBehavior = _readBalanceBehavior;
}
public int getMaxHttpCacheSize() {
return _maxHttpCacheSize;
}
public void setMaxHttpCacheSize(int maxHttpCacheSize) {
assertNotFrozen();
this._maxHttpCacheSize = maxHttpCacheSize;
}
public int getMaxNumberOfRequestsPerSession() {
return _maxNumberOfRequestsPerSession;
}
public void setMaxNumberOfRequestsPerSession(int maxNumberOfRequestsPerSession) {
assertNotFrozen();
_maxNumberOfRequestsPerSession = maxNumberOfRequestsPerSession;
}
/**
* If set to 'true' then it will throw an exception when any query is performed (in session)
* without explicit page size set.
* This can be useful for development purposes to pinpoint all the possible performance bottlenecks
* since from 4.0 there is no limitation for number of results returned from server.
* @return true if should we throw if page size is not set
*/
public boolean isThrowIfQueryPageSizeIsNotSet() {
return _throwIfQueryPageSizeIsNotSet;
}
/**
* If set to 'true' then it will throw an exception when any query is performed (in session)
* without explicit page size set.
* This can be useful for development purposes to pinpoint all the possible performance bottlenecks
* since from 4.0 there is no limitation for number of results returned from server.
* @param throwIfQueryPageSizeIsNotSet value to set
*/
public void setThrowIfQueryPageSizeIsNotSet(boolean throwIfQueryPageSizeIsNotSet) {
assertNotFrozen();
this._throwIfQueryPageSizeIsNotSet = throwIfQueryPageSizeIsNotSet;
}
/**
* Whether UseOptimisticConcurrency is set to true by default for all opened sessions
* @return true if optimistic concurrency is enabled
*/
public boolean isUseOptimisticConcurrency() {
return _useOptimisticConcurrency;
}
/**
* Whether UseOptimisticConcurrency is set to true by default for all opened sessions
* @param useOptimisticConcurrency value to set
*/
public void setUseOptimisticConcurrency(boolean useOptimisticConcurrency) {
assertNotFrozen();
this._useOptimisticConcurrency = useOptimisticConcurrency;
}
public BiFunction getFindJavaClass() {
return _findJavaClass;
}
public void setFindJavaClass(BiFunction _findJavaClass) {
assertNotFrozen();
this._findJavaClass = _findJavaClass;
}
public Function getFindJavaClassName() {
return _findJavaClassName;
}
public void setFindJavaClassName(Function findJavaClassName) {
assertNotFrozen();
_findJavaClassName = findJavaClassName;
}
public Function getFindCollectionName() {
return _findCollectionName;
}
public Function getFindJavaClassByName() {
return _findJavaClassByName;
}
public void setFindJavaClassByName(Function findJavaClassByName) {
_findJavaClassByName = findJavaClassByName;
}
public void setFindCollectionName(Function findCollectionName) {
assertNotFrozen();
_findCollectionName = findCollectionName;
}
public Function getFindIdentityPropertyNameFromCollectionName() {
return _findIdentityPropertyNameFromCollectionName;
}
public void setFindIdentityPropertyNameFromCollectionName(Function findIdentityPropertyNameFromCollectionName) {
assertNotFrozen();
this._findIdentityPropertyNameFromCollectionName = findIdentityPropertyNameFromCollectionName;
}
public BiFunction getDocumentIdGenerator() {
return _documentIdGenerator;
}
public void setDocumentIdGenerator(BiFunction documentIdGenerator) {
assertNotFrozen();
_documentIdGenerator = documentIdGenerator;
}
/**
* Translates the types collection name to the document id prefix
* @return translation function
*/
public Function getTransformClassCollectionNameToDocumentIdPrefix() {
return _transformClassCollectionNameToDocumentIdPrefix;
}
/**
* Translates the types collection name to the document id prefix
* @param transformClassCollectionNameToDocumentIdPrefix value to set
*/
public void setTransformClassCollectionNameToDocumentIdPrefix(Function transformClassCollectionNameToDocumentIdPrefix) {
assertNotFrozen();
this._transformClassCollectionNameToDocumentIdPrefix = transformClassCollectionNameToDocumentIdPrefix;
}
public Function getFindIdentityProperty() {
return _findIdentityProperty;
}
public void setFindIdentityProperty(Function findIdentityProperty) {
assertNotFrozen();
this._findIdentityProperty = findIdentityProperty;
}
public boolean isDisableTopologyUpdates() {
return _disableTopologyUpdates;
}
public void setDisableTopologyUpdates(boolean disableTopologyUpdates) {
assertNotFrozen();
_disableTopologyUpdates = disableTopologyUpdates;
}
public String getIdentityPartsSeparator() {
return _identityPartsSeparator;
}
public void setIdentityPartsSeparator(String identityPartsSeparator) {
assertNotFrozen();
_identityPartsSeparator = identityPartsSeparator;
}
/**
* Saves Enums as integers and instruct the Linq provider to query enums as integer values.
* @return true if we should save enums as integers
*/
public boolean isSaveEnumsAsIntegers() {
return _saveEnumsAsIntegers;
}
/**
* Saves Enums as integers and instruct the Linq provider to query enums as integer values.
* @param saveEnumsAsIntegers value to set
*/
public void setSaveEnumsAsIntegers(boolean saveEnumsAsIntegers) {
assertNotFrozen();
this._saveEnumsAsIntegers = saveEnumsAsIntegers;
}
/**
* Default method used when finding a collection name for a type
* @param clazz Class
* @return default collection name for class
*/
public static String defaultGetCollectionName(Class clazz) {
String result = _cachedDefaultTypeCollectionNames.get(clazz);
if (result != null) {
return result;
}
// we want to reject queries and other operations on abstract types, because you usually
// want to use them for polymorphic queries, and that require the conventions to be
// applied properly, so we reject the behavior and hint to the user explicitly
if (clazz.isInterface()) {
throw new IllegalStateException("Cannot find collection name for interface " + clazz.getName() + ", only concrete classes are supported. Did you forget to customize conventions.findCollectionName?");
}
if (Modifier.isAbstract(clazz.getModifiers())) {
throw new IllegalStateException("Cannot find collection name for abstract class " + clazz.getName() + ", only concrete class are supported. Did you forget to customize conventions.findCollectionName?");
}
result = Inflector.pluralize(clazz.getSimpleName());
_cachedDefaultTypeCollectionNames.put(clazz, result);
return result;
}
/**
* Gets the collection name for a given type.
* @param clazz Class
* @return collection name
*/
public String getCollectionName(Class clazz) {
String collectionName = _findCollectionName.apply(clazz);
if (collectionName != null) {
return collectionName;
}
return defaultGetCollectionName(clazz);
}
/**
* Gets the collection name for a given type.
* @param entity entity to get collection name
* @return collection name
*/
public String getCollectionName(Object entity) {
if (entity == null) {
return null;
}
return getCollectionName(entity.getClass());
}
/**
* Generates the document id.
* @param databaseName Database name
* @param entity Entity
* @return document id
*/
@SuppressWarnings("unchecked")
public String generateDocumentId(String databaseName, Object entity) {
Class> clazz = entity.getClass();
for (Tuple> listOfRegisteredIdConvention : _listOfRegisteredIdConventions) {
if (listOfRegisteredIdConvention.first.isAssignableFrom(clazz)) {
return listOfRegisteredIdConvention.second.apply(databaseName, entity);
}
}
return _documentIdGenerator.apply(databaseName, entity);
}
/**
* Register an id convention for a single type (and all of its derived types.
* Note that you can still fall back to the DocumentIdGenerator if you want.
* @param Entity class
* @param clazz Class
* @param function Function to use
* @return document conventions
*/
@SuppressWarnings("unchecked")
public DocumentConventions registerIdConvention(Class clazz, BiFunction function) {
assertNotFrozen();
_listOfRegisteredIdConventions.stream()
.filter(x -> x.first.equals(clazz))
.findFirst()
.ifPresent(x -> _listOfRegisteredIdConventions.remove(x));
int index;
for (index = 0; index < _listOfRegisteredIdConventions.size(); index++) {
Tuple> entry = _listOfRegisteredIdConventions.get(index);
if (entry.first.isAssignableFrom(clazz)) {
break;
}
}
_listOfRegisteredIdConventions.add(index, Tuple.create(clazz, (BiFunction) function));
return this;
}
/**
* Get the java class (if exists) from the document
* @param id document id
* @param document document to get java class from
* @return java class
*/
public String getJavaClass(String id, ObjectNode document) {
return _findJavaClass.apply(id, document);
}
/**
* Get the class instance by it's name
* @param name class name
* @return java class
*/
public Class getJavaClassByName(String name) {
return _findJavaClassByName.apply(name);
}
/**
* Get the Java class name to be stored in the entity metadata
* @param entityType Entity type
* @return java class name
*/
public String getJavaClassName(Class entityType) {
return _findJavaClassName.apply(entityType);
}
/**
* Clone the current conventions to a new instance
*/
@SuppressWarnings("MethodDoesntCallSuperMethod")
public DocumentConventions clone() {
DocumentConventions cloned = new DocumentConventions();
cloned._listOfRegisteredIdConventions = new ArrayList<>(_listOfRegisteredIdConventions);
cloned._frozen = _frozen;
cloned._originalConfiguration = _originalConfiguration;
cloned._saveEnumsAsIntegers = _saveEnumsAsIntegers;
cloned._identityPartsSeparator = _identityPartsSeparator;
cloned._disableTopologyUpdates = _disableTopologyUpdates;
cloned._findIdentityProperty = _findIdentityProperty;
cloned._transformClassCollectionNameToDocumentIdPrefix = _transformClassCollectionNameToDocumentIdPrefix;
cloned._documentIdGenerator = _documentIdGenerator;
cloned._findIdentityPropertyNameFromCollectionName = _findIdentityPropertyNameFromCollectionName;
cloned._findCollectionName = _findCollectionName;
cloned._findJavaClassName = _findJavaClassName;
cloned._findJavaClass = _findJavaClass;
cloned._findJavaClassByName = _findJavaClassByName;
cloned._useOptimisticConcurrency = _useOptimisticConcurrency;
cloned._throwIfQueryPageSizeIsNotSet = _throwIfQueryPageSizeIsNotSet;
cloned._maxNumberOfRequestsPerSession = _maxNumberOfRequestsPerSession;
cloned._readBalanceBehavior = _readBalanceBehavior;
cloned._maxHttpCacheSize = _maxHttpCacheSize;
cloned._entityMapper = _entityMapper;
cloned._useCompression = _useCompression;
return cloned;
}
private static Field getField(Class> clazz, String name) {
Field field = null;
while (clazz != null && field == null) {
try {
field = clazz.getDeclaredField(name);
} catch (Exception ignored) {
}
clazz = clazz.getSuperclass();
}
return field;
}
/**
* Gets the identity property.
* @param clazz Class of entity
* @return Identity property (field)
*/
public Field getIdentityProperty(Class clazz) {
Field info = _idPropertyCache.get(clazz);
if (info != null) {
return info;
}
try {
Field idField = Arrays.stream(Introspector.getBeanInfo(clazz).getPropertyDescriptors())
.filter(x -> _findIdentityProperty.apply(x))
.findFirst()
.map(x -> getField(clazz, x.getName()))
.orElse(null);
_idPropertyCache.put(clazz, idField);
return idField;
} catch (IntrospectionException e) {
throw new RuntimeException(e);
}
}
public void updateFrom(ClientConfiguration configuration) {
if (configuration == null) {
return;
}
synchronized (this) {
if (configuration.isDisabled() && _originalConfiguration == null) { // nothing to do
return;
}
if (configuration.isDisabled() && _originalConfiguration != null) { // need to revert to original values
_maxNumberOfRequestsPerSession = _originalConfiguration.getMaxNumberOfRequestsPerSession();
_readBalanceBehavior = _originalConfiguration.getReadBalanceBehavior();
_originalConfiguration = null;
return;
}
if (_originalConfiguration == null) {
_originalConfiguration = new ClientConfiguration();
_originalConfiguration.setEtag(-1);
_originalConfiguration.setMaxNumberOfRequestsPerSession(_maxNumberOfRequestsPerSession);
_originalConfiguration.setReadBalanceBehavior(_readBalanceBehavior);
}
_maxNumberOfRequestsPerSession = ObjectUtils.firstNonNull(configuration.getMaxNumberOfRequestsPerSession(), _originalConfiguration.getMaxNumberOfRequestsPerSession());
_readBalanceBehavior = ObjectUtils.firstNonNull(configuration.getReadBalanceBehavior(), _originalConfiguration.getReadBalanceBehavior());
}
}
public static String defaultTransformCollectionNameToDocumentIdPrefix(String collectionName) {
long upperCount = collectionName.chars()
.filter(x -> Character.isUpperCase(x))
.count();
if (upperCount <= 1) {
return collectionName.toLowerCase();
}
// multiple capital letters, so probably something that we want to preserve caps on.
return collectionName;
}
@SuppressWarnings("unchecked")
public void registerQueryValueConverter(Class clazz, IValueForQueryConverter converter) {
assertNotFrozen();
int index;
for (index = 0; index < _listOfQueryValueToObjectConverters.size(); index++) {
Tuple> entry = _listOfQueryValueToObjectConverters.get(index);
if (entry.first.isAssignableFrom(clazz)) {
break;
}
}
_listOfQueryValueToObjectConverters.add(index, Tuple.create(clazz, (fieldName, value, forRange, stringValue) -> {
if (clazz.isInstance(value)) {
return converter.tryConvertValueForQuery(fieldName, (T) value, forRange, stringValue);
}
stringValue.value = null;
return false;
}));
}
public boolean tryConvertValueForQuery(String fieldName, Object value, boolean forRange, Reference
© 2015 - 2024 Weber Informatics LLC | Privacy Policy