-
Notifications
You must be signed in to change notification settings - Fork 175
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
308 additions
and
7 deletions.
There are no files selected for viewing
4 changes: 2 additions & 2 deletions
4
vaadin-spring/src/main/java/com/vaadin/flow/spring/data/CountService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
vaadin-spring/src/main/java/com/vaadin/flow/spring/data/CrudRepositoryService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package com.vaadin.flow.spring.data; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; | ||
import org.springframework.data.repository.CrudRepository; | ||
|
||
import com.vaadin.flow.Nullable; | ||
|
||
/** | ||
* A service that delegates crud operations to a JPA repository. | ||
*/ | ||
public class CrudRepositoryService<T, ID, R extends CrudRepository<T, ID> & JpaSpecificationExecutor<T>> | ||
extends ListRepositoryService<T, ID, R> implements CrudService<T, ID> { | ||
|
||
/* | ||
* Creates the service by autodetecting the type of repository and entity to | ||
* use from the generics. | ||
*/ | ||
public CrudRepositoryService() { | ||
super(); | ||
} | ||
|
||
/** | ||
* Creates the service using the given repository. | ||
* | ||
* @param repository | ||
* the JPA repository | ||
*/ | ||
public CrudRepositoryService(R repository) { | ||
super(repository); | ||
} | ||
|
||
@Override | ||
public @Nullable T save(T value) { | ||
return getRepository().save(value); | ||
} | ||
|
||
/** | ||
* Saves the given objects and returns the (potentially) updated objects. | ||
* <p> | ||
* The returned objects might have new ids or updated consistency versions. | ||
* | ||
* @param values | ||
* the objects to save | ||
* @return the fresh objects | ||
*/ | ||
public List<T> saveAll(Iterable<T> values) { | ||
List<T> saved = new ArrayList<>(); | ||
getRepository().saveAll(values).forEach(saved::add); | ||
return saved; | ||
} | ||
|
||
@Override | ||
public void delete(ID id) { | ||
getRepository().deleteById(id); | ||
} | ||
|
||
/** | ||
* Deletes the objects with the given ids. | ||
* | ||
* @param ids | ||
* the ids of the objects to delete | ||
*/ | ||
public void deleteAll(Iterable<ID> ids) { | ||
getRepository().deleteAllById(ids); | ||
} | ||
|
||
} |
2 changes: 1 addition & 1 deletion
2
vaadin-spring/src/main/java/com/vaadin/flow/spring/data/CrudService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 2 additions & 2 deletions
4
vaadin-spring/src/main/java/com/vaadin/flow/spring/data/FormService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
vaadin-spring/src/main/java/com/vaadin/flow/spring/data/JpaFilterConverter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package com.vaadin.flow.spring.data; | ||
|
||
import jakarta.persistence.EntityManager; | ||
|
||
import com.vaadin.flow.spring.data.filter.AndFilter; | ||
import com.vaadin.flow.spring.data.filter.Filter; | ||
import com.vaadin.flow.spring.data.filter.OrFilter; | ||
import com.vaadin.flow.spring.data.filter.PropertyStringFilter; | ||
import jakarta.persistence.criteria.Path; | ||
import jakarta.persistence.criteria.Root; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.data.jpa.domain.Specification; | ||
import org.springframework.stereotype.Component; | ||
|
||
/** | ||
* Utility class for converting Hilla {@link Filter} specifications into JPA | ||
* filter specifications. This class can be used to implement filtering for | ||
* custom {@link ListService} or {@link CrudService} implementations that use | ||
* JPA as the data source. | ||
* <p> | ||
* This class requires an EntityManager to be available as a Spring bean and | ||
* thus should be injected into the bean that wants to use the converter. | ||
* Manually creating new instances of this class will not work. | ||
*/ | ||
@Component | ||
public class JpaFilterConverter { | ||
|
||
@Autowired | ||
private EntityManager em; | ||
|
||
/** | ||
* Converts the given Hilla filter specification into a JPA filter | ||
* specification for the specified entity class. | ||
* <p> | ||
* If the filter contains {@link PropertyStringFilter} instances, their | ||
* properties, or nested property paths, need to match the structure of the | ||
* entity class. Likewise, their filter values should be in a format that | ||
* can be parsed into the type that the property is of. | ||
* | ||
* @param <T> | ||
* the type of the entity | ||
* @param rawFilter | ||
* the filter to convert | ||
* @param entity | ||
* the entity class | ||
* @return a JPA filter specification for the given filter | ||
*/ | ||
public <T> Specification<T> toSpec(Filter rawFilter, Class<T> entity) { | ||
if (rawFilter == null) { | ||
return Specification.anyOf(); | ||
} | ||
if (rawFilter instanceof AndFilter filter) { | ||
return Specification.allOf(filter.getChildren().stream() | ||
.map(f -> toSpec(f, entity)).toList()); | ||
} else if (rawFilter instanceof OrFilter filter) { | ||
return Specification.anyOf(filter.getChildren().stream() | ||
.map(f -> toSpec(f, entity)).toList()); | ||
} else if (rawFilter instanceof PropertyStringFilter filter) { | ||
Class<?> javaType = extractPropertyJavaType(entity, | ||
filter.getPropertyId()); | ||
return new PropertyStringFilterSpecification<>(filter, javaType); | ||
} else { | ||
if (rawFilter != null) { | ||
throw new IllegalArgumentException("Unknown filter type " | ||
+ rawFilter.getClass().getName()); | ||
} | ||
return Specification.anyOf(); | ||
} | ||
} | ||
|
||
private Class<?> extractPropertyJavaType(Class<?> entity, | ||
String propertyId) { | ||
if (propertyId.contains(".")) { | ||
String[] parts = propertyId.split("\\."); | ||
Root<?> root = em.getCriteriaBuilder().createQuery(entity) | ||
.from(entity); | ||
Path<?> path = root.get(parts[0]); | ||
int i = 1; | ||
while (i < parts.length) { | ||
path = path.get(parts[i]); | ||
i++; | ||
} | ||
return path.getJavaType(); | ||
} else { | ||
return em.getMetamodel().entity(entity).getAttribute(propertyId) | ||
.getJavaType(); | ||
} | ||
} | ||
|
||
} |
139 changes: 139 additions & 0 deletions
139
vaadin-spring/src/main/java/com/vaadin/flow/spring/data/ListRepositoryService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package com.vaadin.flow.spring.data; | ||
|
||
import jakarta.annotation.PostConstruct; | ||
|
||
import java.lang.reflect.Type; | ||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
import com.googlecode.gentyref.GenericTypeReflector; | ||
import com.vaadin.flow.Nullable; | ||
import com.vaadin.flow.spring.data.filter.Filter; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.context.ApplicationContext; | ||
import org.springframework.data.domain.Pageable; | ||
import org.springframework.data.jpa.domain.Specification; | ||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; | ||
import org.springframework.data.repository.CrudRepository; | ||
|
||
/** | ||
* A service that delegates list operations to a JPA repository. | ||
*/ | ||
public class ListRepositoryService<T, ID, R extends CrudRepository<T, ID> & JpaSpecificationExecutor<T>> | ||
implements ListService<T>, GetService<T, ID>, CountService { | ||
|
||
@Autowired | ||
private JpaFilterConverter jpaFilterConverter; | ||
|
||
@Autowired | ||
private ApplicationContext applicationContext; | ||
|
||
private R repository; | ||
private final Class<T> entityClass; | ||
|
||
/* | ||
* Creates the service by autodetecting the type of repository and entity to | ||
* use from the generics. | ||
*/ | ||
public ListRepositoryService() { | ||
this.entityClass = resolveEntityClass(); | ||
} | ||
|
||
/** | ||
* Creates the service using the given repository. | ||
* | ||
* @param repository | ||
* the JPA repository | ||
*/ | ||
public ListRepositoryService(R repository) { | ||
this.repository = repository; | ||
this.entityClass = resolveEntityClass(); | ||
} | ||
|
||
@PostConstruct | ||
private void init() { | ||
if (repository == null) { | ||
repository = resolveRepository(); | ||
} | ||
} | ||
|
||
/** | ||
* Accessor for the repository instance. | ||
* | ||
* @return the repository instance | ||
*/ | ||
protected R getRepository() { | ||
return repository; | ||
} | ||
|
||
@Override | ||
public List<T> list(Pageable pageable, @Nullable Filter filter) { | ||
Specification<T> spec = toSpec(filter); | ||
return getRepository().findAll(spec, pageable).getContent(); | ||
} | ||
|
||
@Override | ||
public Optional<T> get(ID id) { | ||
return getRepository().findById(id); | ||
} | ||
|
||
@Override | ||
public boolean exists(ID id) { | ||
return getRepository().existsById(id); | ||
} | ||
|
||
/** | ||
* Counts the number of entities that match the given filter. | ||
* | ||
* @param filter | ||
* the filter, or {@code null} to use no filter | ||
* @return | ||
*/ | ||
@Override | ||
public long count(@Nullable Filter filter) { | ||
return getRepository().count(toSpec(filter)); | ||
} | ||
|
||
/** | ||
* Converts the given filter to a JPA specification. | ||
* | ||
* @param filter | ||
* the filter to convert | ||
* @return a JPA specification | ||
*/ | ||
protected Specification<T> toSpec(@Nullable Filter filter) { | ||
return jpaFilterConverter.toSpec(filter, entityClass); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
private R resolveRepository() { | ||
var repositoryTypeParam = ListRepositoryService.class | ||
.getTypeParameters()[2]; | ||
Type entityType = GenericTypeReflector.getTypeParameter(getClass(), | ||
repositoryTypeParam); | ||
if (entityType == null) { | ||
throw new IllegalStateException(String.format( | ||
"Unable to detect the type for the class '%s' in the " | ||
+ "class '%s'.", | ||
repositoryTypeParam, getClass())); | ||
} | ||
Class<R> repositoryClass = (Class<R>) GenericTypeReflector | ||
.erase(entityType); | ||
return applicationContext.getBean(repositoryClass); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
protected Class<T> resolveEntityClass() { | ||
var entityTypeParam = ListRepositoryService.class | ||
.getTypeParameters()[0]; | ||
Type entityType = GenericTypeReflector.getTypeParameter(getClass(), | ||
entityTypeParam); | ||
if (entityType == null) { | ||
throw new IllegalStateException(String.format( | ||
"Unable to detect the type for the class '%s' in the " | ||
+ "class '%s'.", | ||
entityTypeParam, getClass())); | ||
} | ||
return (Class<T>) GenericTypeReflector.erase(entityType); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters