Skip to content

Commit

Permalink
Merge pull request #518 from eclipse/NOSQL-516
Browse files Browse the repository at this point in the history
Enable Custom Repository at Eclipse JNoSQL
  • Loading branch information
otaviojava authored Jun 2, 2024
2 parents ace062f + 28f3f61 commit e786f1b
Show file tree
Hide file tree
Showing 33 changed files with 1,778 additions and 72 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ and this project adheres to https://semver.org/spec/v2.0.0.html[Semantic Version

== [Unreleased]

=== Added

- Enables custom Repository

== [1.1.1] - 2023-05-25

=== Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ public interface ClassScanner {
*/
Set<Class<?>> repositoriesStandard();

/**
* Returns a set of custom repository interfaces that are not standard repositories.
*
* @return A set of custom repository interfaces.
*/
Set<Class<?>> customRepositories();

/**
* Loads and returns an instance of the {@link ClassScanner} implementation using the ServiceLoader mechanism.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright (c) 2022 Contributors to the Eclipse Foundation
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php.
*
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
*
* Otavio Santana
*/
package org.eclipse.jnosql.mapping.column.query;

import jakarta.enterprise.context.spi.CreationalContext;
import org.eclipse.jnosql.mapping.DatabaseQualifier;
import org.eclipse.jnosql.mapping.DatabaseType;
import org.eclipse.jnosql.mapping.column.ColumnTemplate;
import org.eclipse.jnosql.mapping.core.Converters;
import org.eclipse.jnosql.mapping.core.spi.AbstractBean;
import org.eclipse.jnosql.mapping.core.util.AnnotationLiteralUtil;
import org.eclipse.jnosql.mapping.metadata.EntitiesMetadata;
import org.eclipse.jnosql.mapping.semistructured.query.CustomRepositoryHandler;

import java.lang.annotation.Annotation;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;


/**
* This class serves as a JNoSQL discovery bean for CDI extension, responsible for registering Custom Repository instances.
* It extends {@link AbstractBean} and is parameterized with type {@code T} representing the repository type.
* <p>
* Upon instantiation, it initializes with the provided repository type, provider name, and qualifiers.
* The provider name specifies the database provider for the repository.
* </p>
*
* @param <T> the type of the repository
* @see AbstractBean
*/
public class CustomRepositoryColumnBean<T> extends AbstractBean<T> {

private final Class<T> type;

private final Set<Type> types;

private final String provider;

private final Set<Annotation> qualifiers;

/**
* Constructor
*
* @param type the tye
* @param provider the provider name, that must be a
*/
@SuppressWarnings("unchecked")
public CustomRepositoryColumnBean(Class<?> type, String provider) {
this.type = (Class<T>) type;
this.types = Collections.singleton(type);
this.provider = provider;
if (provider.isEmpty()) {
this.qualifiers = new HashSet<>();
qualifiers.add(DatabaseQualifier.ofColumn());
qualifiers.add(AnnotationLiteralUtil.DEFAULT_ANNOTATION);
qualifiers.add(AnnotationLiteralUtil.ANY_ANNOTATION);
} else {
this.qualifiers = Collections.singleton(DatabaseQualifier.ofColumn(provider));
}
}

@Override
public Class<?> getBeanClass() {
return type;
}

@SuppressWarnings("unchecked")
@Override
public T create(CreationalContext<T> context) {
var entities = getInstance(EntitiesMetadata.class);
var template = provider.isEmpty() ? getInstance(ColumnTemplate.class) :
getInstance(ColumnTemplate.class, DatabaseQualifier.ofColumn(provider));

var converters = getInstance(Converters.class);

var handler = CustomRepositoryHandler.builder()
.entitiesMetadata(entities)
.template(template)
.customRepositoryType(type)
.converters(converters)
.build();

return (T) Proxy.newProxyInstance(type.getClassLoader(),
new Class[]{type},
handler);
}


@Override
public Set<Type> getTypes() {
return types;
}

@Override
public Set<Annotation> getQualifiers() {
return qualifiers;
}

@Override
public String getId() {
return type.getName() + '@' + DatabaseType.COLUMN + "-" + provider;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.eclipse.jnosql.communication.semistructured.DatabaseManager;
import org.eclipse.jnosql.mapping.DatabaseMetadata;
import org.eclipse.jnosql.mapping.Databases;
import org.eclipse.jnosql.mapping.column.query.CustomRepositoryColumnBean;
import org.eclipse.jnosql.mapping.column.query.RepositoryColumnBean;
import org.eclipse.jnosql.mapping.metadata.ClassScanner;

Expand Down Expand Up @@ -51,8 +52,10 @@ void onAfterBeanDiscovery(@Observes final AfterBeanDiscovery afterBeanDiscovery)

ClassScanner scanner = ClassScanner.load();
Set<Class<?>> crudTypes = scanner.repositoriesStandard();
LOGGER.info(String.format("Processing Column extension: %d databases crud %d found",
databases.size(), crudTypes.size()));
Set<Class<?>> customRepositories = scanner.customRepositories();

LOGGER.info(String.format("Processing Column extension: %d databases crud %d found, custom repositories: %d",
databases.size(), crudTypes.size(), customRepositories.size()));
LOGGER.info("Processing repositories as a Column implementation: " + crudTypes);

databases.forEach(type -> {
Expand All @@ -70,6 +73,15 @@ void onAfterBeanDiscovery(@Observes final AfterBeanDiscovery afterBeanDiscovery)
.addBean(new RepositoryColumnBean<>(type, database.getProvider())));
});


customRepositories.forEach(type -> {
if (!databases.contains(DatabaseMetadata.DEFAULT_COLUMN)) {
afterBeanDiscovery.addBean(new CustomRepositoryColumnBean<>(type, ""));
}
databases.forEach(database ->
afterBeanDiscovery.addBean(new CustomRepositoryColumnBean<>(type, database.getProvider())));
});

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2022 Contributors to the Eclipse Foundation
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php.
*
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
*
* Otavio Santana
*/
package org.eclipse.jnosql.mapping.column.entities;

import jakarta.data.repository.Insert;
import jakarta.data.repository.Repository;


@Repository
public interface People {

@Insert
Person insert(Person person);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2022 Contributors to the Eclipse Foundation
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php.
*
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
*
* Otavio Santana
*/
package org.eclipse.jnosql.mapping.column.spi;

import jakarta.inject.Inject;
import org.assertj.core.api.SoftAssertions;
import org.eclipse.jnosql.mapping.Database;
import org.eclipse.jnosql.mapping.DatabaseType;
import org.eclipse.jnosql.mapping.column.ColumnTemplate;
import org.eclipse.jnosql.mapping.column.MockProducer;
import org.eclipse.jnosql.mapping.column.entities.People;
import org.eclipse.jnosql.mapping.column.entities.Person;
import org.eclipse.jnosql.mapping.core.Converters;
import org.eclipse.jnosql.mapping.core.spi.EntityMetadataExtension;
import org.eclipse.jnosql.mapping.reflection.Reflections;
import org.eclipse.jnosql.mapping.semistructured.EntityConverter;
import org.jboss.weld.junit5.auto.AddExtensions;
import org.jboss.weld.junit5.auto.AddPackages;
import org.jboss.weld.junit5.auto.EnableAutoWeld;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertNotNull;


@EnableAutoWeld
@AddPackages(value = {Converters.class, EntityConverter.class, ColumnTemplate.class})
@AddPackages(MockProducer.class)
@AddPackages(Reflections.class)
@AddExtensions({EntityMetadataExtension.class, ColumnExtension.class})
class ColumnCustomExtensionTest {

@Inject
@Database(value = DatabaseType.COLUMN)
private People people;

@Inject
@Database(value = DatabaseType.COLUMN, provider = "columnRepositoryMock")
private People pepoleMock;

@Inject
private People repository;


@Test
void shouldInitiate() {
assertNotNull(people);
Person person = people.insert(Person.builder().build());
SoftAssertions.assertSoftly(soft -> {
soft.assertThat(person).isNotNull();
soft.assertThat(person.getName()).isEqualTo("Default");
});
}

@Test
void shouldUseMock(){
assertNotNull(pepoleMock);

Person person = pepoleMock.insert(Person.builder().build());
SoftAssertions.assertSoftly(soft -> {
soft.assertThat(person).isNotNull();
soft.assertThat(person.getName()).isEqualTo("columnRepositoryMock");
});
}

@Test
void shouldUseDefault(){
assertNotNull(repository);

Person person = repository.insert(Person.builder().build());
SoftAssertions.assertSoftly(soft -> {
soft.assertThat(person).isNotNull();
soft.assertThat(person.getName()).isEqualTo("Default");
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
*/
package org.eclipse.jnosql.mapping.core.query;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
Expand All @@ -41,8 +43,9 @@ public Object invoke(Operation operation) {
List<?> result = operation.repository.insertAll(stream(entities.spliterator(), false).toList());
return returnInsert(returnType, result);
} else if (param.getClass().isArray()) {
Iterable<?> result = operation.repository.insertAll(Arrays.asList((Object[]) param));
return returnInsert(returnType, result);
Iterable<?> result = operation.repository.insertAll(getArray(param));

return returnInsert(returnType, iterableToArray(param, result));
} else {
var result = operation.repository.insert(param);
return returnInsert(returnType, result);
Expand Down Expand Up @@ -73,7 +76,7 @@ public Object invoke(Operation operation) {
if (param instanceof Iterable entities) {
return executeIterable(operation, entities, returnType, false, null);
} else if (param.getClass().isArray()) {
List<Object> entities = Arrays.asList((Object[]) param);
List<Object> entities = getArray(param);
return executeIterable(operation, entities, returnType, true, param);
} else {
return executeSingleEntity(operation, param, returnType);
Expand All @@ -93,7 +96,7 @@ private static Object executeIterable(Operation operation, Iterable entities, Re
} else if (returnType.isLong()) {
return (long) result.size();
} else if (isArray) {
return result.toArray();
return iterableToArray(param, result);
} else {
return entities;
}
Expand Down Expand Up @@ -126,7 +129,7 @@ public Object invoke(Operation operation) {
if (param instanceof Iterable entities) {
return executeIterable(operation, entities, returnType);
} else if (param.getClass().isArray()) {
List<Object> entities = Arrays.asList((Object[]) param);
List<Object> entities = getArray(param);
return executeIterable(operation, entities, returnType);
} else {
return executeSingleEntity(operation, param, returnType);
Expand Down Expand Up @@ -175,15 +178,35 @@ public Object invoke(Operation operation) {
Iterable<?> result = operation.repository.saveAll(stream(entities.spliterator(), false).toList());
return returnType.isVoid() ? Void.TYPE : result;
} else if (param.getClass().isArray()) {
Iterable<?> result = operation.repository.saveAll(Arrays.asList((Object[]) param));
return returnType.isVoid() ? Void.TYPE : result;
Iterable<?> result = operation.repository.saveAll(getArray(param));
return returnType.isVoid() ? Void.TYPE : iterableToArray(param, result);
} else {
Object result = operation.repository.save(param);
return returnType.isVoid() ? Void.TYPE : result;
}
}
};

private static Object iterableToArray(Object param, Iterable<?> result) {
List<Object> elements = new ArrayList<>();
for (Object o : result) {
elements.add(o);
}
Object newArray = Array.newInstance(param.getClass().getComponentType(), elements.size());
return elements.toArray((Object[]) newArray);
}

private static List<Object> getArray(Object param) {
List<Object> entities = new ArrayList<>();
int length = Array.getLength(param);
for (int index = 0; index < length; index++) {
Object element = Array.get(param, index);
entities.add(element);
}

return entities;
}

private static void checkParameterNumber(Operation operation) {
if (operation.params.length != 1) {
throw new UnsupportedOperationException("The method operation requires one parameter, please check the method: "
Expand Down
Loading

0 comments on commit e786f1b

Please sign in to comment.