Skip to content

Fixes #966 #967

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@

- Nitrite now supports JPMS. It is now modular and can be used in Java 9 or above.
- Version upgrade for several dependencies
- Repository type validation can be disabled in `NitriteBuilder` as a fix for #966

### Issue Fixes

- Fix for #935
- Fix for #948
- Fix for #961
- Fix for #966

## Release 4.2.2 - Mar 5, 2024

Expand Down
23 changes: 22 additions & 1 deletion nitrite/src/main/java/org/dizitart/no2/NitriteBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
* @see Nitrite
* @since 1.0
*/
@Getter
public class NitriteBuilder {
@Getter
/**
* The Nitrite configuration object.
*/
Expand Down Expand Up @@ -61,6 +61,27 @@ public NitriteBuilder fieldSeparator(String separator) {
return this;
}

/**
* Disables the repository type validation for the Nitrite database.
* <p>
* Repository type validation is a feature in Nitrite that ensures the type of the objects
* stored in the repository can be converted to and from {@link org.dizitart.no2.collection.Document}.
* <p>
* By default, the repository type validation is enabled. If you disable it, and if you try to
* store an object that cannot be converted to a {@link org.dizitart.no2.collection.Document},
* then Nitrite will throw an exception during the operation.
*
* @return the NitriteBuilder instance with repository type validation disabled
* @see org.dizitart.no2.collection.Document
* @see org.dizitart.no2.repository.ObjectRepository
* @see org.dizitart.no2.common.mapper.EntityConverter
* @since 4.3.0
*/
public NitriteBuilder disableRepositoryTypeValidation() {
this.nitriteConfig.disableRepositoryTypeValidation();
return this;
}

/**
* Registers an {@link EntityConverter} with the Nitrite database.
* An {@link EntityConverter} is used to convert between an entity and a
Expand Down
22 changes: 21 additions & 1 deletion nitrite/src/main/java/org/dizitart/no2/NitriteConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ public class NitriteConfig implements AutoCloseable {
*/
private Integer schemaVersion = Constants.INITIAL_SCHEMA_VERSION;

@Getter
/**
* Indicates if repository type validation is disabled.
*/
private boolean repositoryTypeValidationDisabled = false;

/**
* Instantiates a new {@link NitriteConfig}.
*/
Expand All @@ -103,6 +109,20 @@ public void fieldSeparator(String separator) {
NitriteConfig.fieldSeparator = separator;
}

/**
* Disables repository type validation.
*
* @throws InvalidOperationException if the repository type validation is attempted to be
* changed after database initialization.
*/
public void disableRepositoryTypeValidation() {
if (configured) {
throw new InvalidOperationException("Cannot change repository type validation after database" +
" initialization");
}
this.repositoryTypeValidationDisabled = true;
}

/**
* Registers an {@link EntityConverter} with the Nitrite database.
*
Expand Down Expand Up @@ -157,7 +177,7 @@ public NitriteConfig addMigration(Migration migration) {
TreeMap<Integer, Migration> targetMap = migrations.computeIfAbsent(start, k -> new TreeMap<>());
Migration existing = targetMap.get(end);
if (existing != null) {
log.warn("Overriding migration " + existing + " with " + migration);
log.warn("Overriding migration {} with {}", existing, migration);
}
targetMap.put(end, migration);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.dizitart.no2.common.util;

import org.dizitart.no2.NitriteConfig;
import org.dizitart.no2.collection.Document;
import org.dizitart.no2.common.mapper.NitriteMapper;
import org.dizitart.no2.exceptions.IndexingException;
Expand Down Expand Up @@ -149,22 +150,25 @@ public static void validateProjectionType(Class<?> type, NitriteMapper nitriteMa
}
}

public static void validateRepositoryType(Class<?> type, NitriteMapper nitriteMapper) {
public static void validateRepositoryType(Class<?> type, NitriteConfig nitriteConfig) {
Object value;
try {
if (type.isInterface() || (Modifier.isAbstract(type.getModifiers()) && !isBuiltInValueType(type))) {
// defer validation during insertion
return;
}

value = newInstance(type, false, nitriteMapper);
if (value == null) {
throw new ValidationException("Cannot create new instance of type " + type);
}

Document document = (Document) nitriteMapper.tryConvert(value, Document.class);
if (document == null || document.size() == 0) {
throw new ValidationException("Cannot convert to document from type " + type);
if (!nitriteConfig.isRepositoryTypeValidationDisabled()) {
NitriteMapper nitriteMapper = nitriteConfig.nitriteMapper();
value = newInstance(type, false, nitriteMapper);
if (value == null) {
throw new ValidationException("Cannot create new instance of type " + type);
}

Document document = (Document) nitriteMapper.tryConvert(value, Document.class);
if (document == null || document.size() == 0) {
throw new ValidationException("Cannot convert to document from type " + type);
}
}
} catch (Exception e) {
throw new ValidationException("Invalid repository type", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import org.dizitart.no2.NitriteConfig;
import org.dizitart.no2.collection.CollectionFactory;
import org.dizitart.no2.collection.NitriteCollection;
import org.dizitart.no2.common.mapper.NitriteMapper;
import org.dizitart.no2.common.util.StringUtils;
import org.dizitart.no2.exceptions.NitriteIOException;
import org.dizitart.no2.exceptions.ValidationException;
Expand Down Expand Up @@ -133,10 +132,9 @@ public void clear() {

private <T> ObjectRepository<T> createRepository(NitriteConfig nitriteConfig, Class<T> type,
String collectionName, String key) {
NitriteMapper nitriteMapper = nitriteConfig.nitriteMapper();
NitriteStore<?> store = nitriteConfig.getNitriteStore();

validateRepositoryType(type, nitriteMapper);
validateRepositoryType(type, nitriteConfig);

if (store.getCollectionNames().contains(collectionName)) {
throw new ValidationException("A collection with same entity name already exists");
Expand All @@ -154,14 +152,13 @@ private <T> ObjectRepository<T> createRepository(NitriteConfig nitriteConfig, Cl
private <T> ObjectRepository<T> createRepositoryByDecorator(NitriteConfig nitriteConfig,
EntityDecorator<T> entityDecorator,
String collectionName, String key) {
NitriteMapper nitriteMapper = nitriteConfig.nitriteMapper();
NitriteStore<?> store = nitriteConfig.getNitriteStore();

if (store.getCollectionNames().contains(collectionName)) {
throw new ValidationException("A collection with same entity name already exists");
}

validateRepositoryType(entityDecorator.getEntityType(), nitriteMapper);
validateRepositoryType(entityDecorator.getEntityType(), nitriteConfig);

NitriteCollection nitriteCollection = collectionFactory.getCollection(collectionName,
nitriteConfig, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.dizitart.no2.common.util;

import org.dizitart.no2.NitriteConfig;
import org.dizitart.no2.common.mapper.SimpleNitriteMapper;
import org.dizitart.no2.exceptions.ValidationException;
import org.dizitart.no2.integration.Retry;
Expand Down Expand Up @@ -96,18 +97,19 @@ public void testValidateProjectionType() {

@Test
public void testValidateRepositoryType() {
SimpleNitriteMapper documentMapper = new SimpleNitriteMapper();
documentMapper.registerEntityConverter(new ClassA.ClassAConverter());
documentMapper.registerEntityConverter(new ClassBConverter());
documentMapper.registerEntityConverter(new EmptyClass.Converter());
NitriteConfig nitriteConfig = new NitriteConfig();
nitriteConfig.registerEntityConverter(new ClassA.ClassAConverter());
nitriteConfig.registerEntityConverter(new ClassBConverter());
nitriteConfig.registerEntityConverter(new EmptyClass.Converter());
nitriteConfig.autoConfigure();

validateRepositoryType(ClassA.class, documentMapper);
validateRepositoryType(ClassA.class, nitriteConfig);

assertThrows(ValidationException.class, () -> validateRepositoryType(EmptyClass.class, documentMapper));
assertThrows(ValidationException.class, () -> validateRepositoryType(ClassC.class, documentMapper));
assertThrows(ValidationException.class, () -> validateRepositoryType(String.class, documentMapper));
assertThrows(ValidationException.class, () -> validateRepositoryType(Number.class, documentMapper));
assertThrows(ValidationException.class, () -> validateRepositoryType(Integer.class, documentMapper));
assertThrows(ValidationException.class, () -> validateRepositoryType(Object.class, documentMapper));
assertThrows(ValidationException.class, () -> validateRepositoryType(EmptyClass.class, nitriteConfig));
assertThrows(ValidationException.class, () -> validateRepositoryType(ClassC.class, nitriteConfig));
assertThrows(ValidationException.class, () -> validateRepositoryType(String.class, nitriteConfig));
assertThrows(ValidationException.class, () -> validateRepositoryType(Number.class, nitriteConfig));
assertThrows(ValidationException.class, () -> validateRepositoryType(Integer.class, nitriteConfig));
assertThrows(ValidationException.class, () -> validateRepositoryType(Object.class, nitriteConfig));
}
}
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
<commons-io.version>2.16.1</commons-io.version>
<kotlin.version>1.9.24</kotlin.version>
<kotlinx-serialization.version>1.6.3</kotlinx-serialization.version>
<kotlinx-datetime.version>0.6.0-RC.2</kotlinx-datetime.version>
<lombok.version>1.18.32</lombok.version>
<lombok-maven-plugin.version>1.18.20.0</lombok-maven-plugin.version>
<junit.version>4.13.2</junit.version>
Expand Down Expand Up @@ -291,6 +292,12 @@
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-datetime-jvm</artifactId>
<version>${kotlinx-datetime.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down
5 changes: 5 additions & 0 deletions potassium-nitrite/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@
<artifactId>log4j-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-datetime-jvm</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
21 changes: 21 additions & 0 deletions potassium-nitrite/src/main/kotlin/org/dizitart/kno2/Builder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ class Builder internal constructor() {
*/
var fieldSeparator: String = NitriteConfig.getFieldSeparator()

/**
* Enables/disables the repository type validation for the Nitrite database.
* <p>
* Repository type validation is a feature in Nitrite that ensures the type of the objects
* stored in the repository can be converted to and from [org.dizitart.no2.collection.Document].
* <p>
* By default, the repository type validation is enabled. If you disable it, and if you try to
* store an object that cannot be converted to a [org.dizitart.no2.collection.Document],
* then Nitrite will throw an exception during the operation.
*
* @see org.dizitart.no2.collection.Document
* @see org.dizitart.no2.repository.ObjectRepository
* @see org.dizitart.no2.common.mapper.EntityConverter
* @since 4.3.0
*/
var enableRepositoryValidation = true

/**
* Loads a [NitriteModule] into the Nitrite database. The module can be used to extend the
* functionality of Nitrite.
Expand Down Expand Up @@ -88,6 +105,10 @@ class Builder internal constructor() {
builder.schemaVersion(schemaVersion)
}

if (!enableRepositoryValidation) {
builder.disableRepositoryTypeValidation()
}

if (entityConverters.isNotEmpty()) {
entityConverters.forEach { builder.registerEntityConverter(it) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import org.dizitart.kno2.filters.eq
import org.dizitart.kno2.serialization.KotlinXSerializationMapper
import org.dizitart.no2.collection.Document
import org.dizitart.no2.common.module.NitriteModule.module
import org.dizitart.no2.exceptions.ValidationException
import org.dizitart.no2.mvstore.MVStoreModule
import org.junit.Test
import org.slf4j.LoggerFactory
Expand Down Expand Up @@ -180,4 +181,51 @@ class KotlinXSerializationMapperTest {
testData.someArray.forEachIndexed { index, s -> assertEquals(decodedObject.someArray[index], s) }
assertEquals(testData, decodedObject.copy(someArray = testData.someArray))
}
}

@Test(expected = ValidationException::class)
fun testRepositoryValidationEnabled() {
val db = nitrite {
loadModule(MVStoreModule(dbPath))
loadModule(module(KotlinXSerializationMapper()))
}

val repo = db.getRepository<CacheEntry>()
repo.insert(CacheEntry("sha256", kotlinx.datetime.Clock.System.now()))
repo.find(CacheEntry::sha256 eq "sha256").firstOrNull().also {
assertEquals(it?.sha256, "sha256")
}
db.close()
try {
Files.delete(Paths.get(dbPath))
} catch (e: Exception) {
log.error("Failed to delete db file", e)
}
}

@Test
fun testRepositoryValidationDisabled() {
val db = nitrite {
enableRepositoryValidation = false
loadModule(MVStoreModule(dbPath))
loadModule(module(KotlinXSerializationMapper()))
}

val repo = db.getRepository<CacheEntry>()
repo.insert(CacheEntry("sha256", kotlinx.datetime.Clock.System.now()))
repo.find(CacheEntry::sha256 eq "sha256").firstOrNull().also {
assertEquals(it?.sha256, "sha256")
}
db.close()
try {
Files.delete(Paths.get(dbPath))
} catch (e: Exception) {
log.error("Failed to delete db file", e)
}
}
}

@Serializable
data class CacheEntry(
val sha256: String,
val lastUpdated: kotlinx.datetime.Instant
)