I have a generic Spring Data repository interface that extends QuerydslBinderCustomizer, allowing me to customize the query execution. I am trying to extend the basic equality testing built into the default repository implementation, so that I can perform other query operations using Spring Data REST. For example:
GET /api/persons?name=Joe%20Smith // This works by default
GET /api/persons?nameEndsWith=Smith // This requires custom parameter binding.
The problem I am having is that every alias of an entity path I create seems to override the preceding alias bindings.
#NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable>
extends PagingAndSortingRepository<T, ID>, QueryDslPredicateExecutor<T>, QuerydslBinderCustomizer {
#Override
#SuppressWarnings("unchecked")
default void customize(QuerydslBindings bindings, EntityPath entityPath){
Class<T> model = entityPath.getType();
Path<T> root = entityPath.getRoot();
for (Field field: model.getDeclaredFields()){
if (field.isSynthetic()) continue;
Class<?> fieldType = field.getType();
if (fieldType.isAssignableFrom(String.class)){
// This binding works by itself, but not after the next one is added
bindings.bind(Expressions.stringPath(root, field.getName()))
.as(field.getName() + "EndsWith")
.first((path, value) -> {
return path.endsWith(value);
});
// This binding overrides the previous one
bindings.bind(Expressions.stringPath(root, field.getName()))
.as(field.getName() + "StartsWith")
.first((path, value) -> {
return path.startsWith(value);
});
}
}
}
}
Is it possible to create more than one alias for the same field? Can this be accomplished in a generic way?
You can create a transient property bound to QueryDSL this way:
#Transient
#QueryType(PropertyType.SIMPLE)
public String getNameEndsWith() {
// Whatever code, even return null
}
If you are using the QueryDSL annotation processor, you will see the "nameEndsWith" in the metadata Qxxx class, so you can bind it like any persisted property, but without persisting it.
Related
I'm looking a way to bind Type for specific entity fields during entity manager configuration phase. I need it to be able to apply extra "rules" to target entity field using external source without entity class changes.
So basically I'm trying to avoid hardcode #Type annotation way as below:
#Type(type = foo.package.MyType, parameters = {
#Parameter(name = "fooProperty", value = "fooValue")
})
private String someField;
Instead I would like to set Type for someField while building model programmatically.
Here's one way I've seen before. It is a little low-level, so I suspect there is a cleaner way to do this.
This uses a custom Persister in Hibernate to allow us to substitute the type while the SessionFactory ( EntityManagerFactory ) is being created.
First, the #Persister annotation is used to declare the custom Persister :
#Entity
#Persister(impl = MyPersister.class)
public class EntityWithPersister {
private String someField;
Then normally the custom persister should extend SingleTableEntityPersister in Hibernate. If the entity is using a different #Inheritance(strategy), then it may need to extend JoinedSubclassEntityPersister or UnionSubclassEntityPersister instead.
This offers the chance to change a type at the point of construction, for example:
public class MyPersister extends SingleTableEntityPersister {
public MyPersister(PersistentClass persistentClass,
EntityDataAccess cacheAccessStrategy,
NaturalIdDataAccess naturalIdRegionAccessStrategy,
PersisterCreationContext creationContext)
throws HibernateException {
super(modify(persistentClass), cacheAccessStrategy,
naturalIdRegionAccessStrategy, creationContext);
}
private static PersistentClass modify(PersistentClass persistentClass) {
SimpleValue value = (SimpleValue) persistentClass
.getProperty("someField").getValue();
value.setTypeName(MyType.class.getName());
return persistentClass;
}
}
If you need to access more of the context you are in, creationContext.getSessionFactory() is probably a good starting point.
Having two types of entities, that are mapped to two Java classes in the single MongoDB collection:
#Document
public class Superclass { ... }
#Document(collection = "superclass")
public class Subclass extends Superclass { ... }
and two repositories for those entities:
public interface SuperclassRepository extends MongoRepository<Superclass, String> {}
public interface SubclassRepository extends MongoRepository<Subclass, String> {}
MongoRepositories don't handle the inheritance of the entities correctly. While querying for all Subclass objects (e.g. SubclassRepository.findAll()) the result set contains Superclass objects, that are instantiated (or at least had been tried to be instantiated) with null values for fields that are part of the Subclass, but are not part of the Superclass.
The expected result would be that SubclassRepository should return only Subclass objects, while SuperclassRepository should return Superclass and Subclass objects. It works this way in Spring Data JPA.
Has anyone encountered this bug and has any solution on how to fix it?
I encounter the same issue.
Take a look at the source code and at my surprise it is kind of not implemented. It adds the Collection name and the entity class but not inserted in the final query the _class property.
And after taking a look at it I realized that how Mongo would know that SubClass1 or Subclass2 derived from SuperClass.
So I just override the SimpleMongoRepository Class and create my own Factory that put that class instead of the default SimpleMongoRepository
Here what i added:
public MySimpleMongoRepository(MongoEntityInformation<T, ID> metadata, MongoOperations mongoOperations) {
Assert.notNull(mongoOperations);
Assert.notNull(metadata);
this.entityInformation = metadata;
this.mongoOperations = mongoOperations;
Reflections reflections = new Reflections("com.cre8techlabs.entity");
Set<String> subTypes = reflections.getSubTypesOf(entityInformation.getJavaType()).stream().map(Class::getName).collect(Collectors.toSet());
subTypes.add(entityInformation.getJavaType().getName());
this.baseClassQuery = Criteria.where("_class").in(subTypes.toArray());
}
And here an example of implementation of a find
public T findOne(ID id) {
Assert.notNull(id, "The given id must not be null!");
Query q = getIdQuery(id).addCriteria(baseClassQuery);
return mongoOperations.findOne(q, entityInformation.getJavaType(), entityInformation.getCollectionName());
}
It works for me, I am just afraid that it take a little longer
I am developing a framework which allows developers to do database operations through service layer. Service classes will send the database request dto object which will be annotated with sql ID to use as ID in MyBatis. Later I will read the annotation value by reflection.
First of all, I created a custom annotation interface.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface MyBatisMapper {
String namespace() default "";
String sqlId() default "";
}
And interface for database request dto object.
public interface IReqDto {
public String getDaoType();
}
And database request dto object which will implement the above IReqDto interface.
#MyBatisMapper(namespace="User", sqlId="userInsert")
public class UserInsertReqDto implements IReqDto{
//beans and getters/setters
}
The above bean may vary as requirement of the developer. This is not part of the framework. Developer must implement IReqDto interface in any kind of database request object he use.
What I am trying is to read the annotated values (namespace and sqlId) from database invoker class by using reflection.
I understand that I can get the annotated value by doing this.
Class<UserInsertReqDto> ReqDto = UserInsertReqDto.class;
for(Annotation annotation : ReqDto.getAnnotations()) {
System.out.println(annotation.toString());
}
But my problem is, as the UserInsertReqDto will vary, I tried to use reflection to IReqDto interface.
Class<IReqDto> ReqDto = IReqDto.class;
Well, surely it doesn't work.
The question is - how can I read the annotated value from database request object in this situation? Thanks.
Maybe I'm still misunderstanding your question, so correct me if necessary.
You will be given an object of a custom implementation of ReqDto
ReqDto object = ...; // get instance
Class<?> clazz = object.getClass(); get actual type of the instance
for(Annotation annotation : clazz.getAnnotations()) { // these are class annotations
System.out.println(annotation.toString());
}
or
MyBatisMapper mapperAnnotation = clazz.getAnnotation(MyBatisMapper.class);
if (mapperAnnotation != null) {
System.out.println(mapperAnnotation.namespace()
System.out.println(mapperAnnotation.sqlId()
}
Reflection works regardless of the type. So, instead of referring to the concrete class, simply use Object#getClass() and/or Class<?>. E.g.
public Metadata getMetadata(Object pojo) {
Annotation annotation = pojo.getAnnotation(MyBatisMapper.class);
if (annotation == null) {
return null;
}
return new Metadata(annotation.getNamespcae(), annotation.getSqlId());
}
where Metadata is just a value class that you can use later on that contains the values about the object. You can also directly work with the MyBatisWrapper annotation.
I'm trying to make an application extensible by using CDI, but it seems like I'm missing a piece of the puzzle.
What I want:
Have a global configuration that will define which implementation of an interface to use. The implementations would have annotations like #ImplDescriptor(type="type1").
What I tried:
#Produces
public UEInterface createUserExit(#Any Instance<UEInterface> instance, InjectionPoint ip) {
Annotated annotated = ip.getAnnotated();
UESelect ueSelect = annotated.getAnnotation(UESelect.class);
if (ueSelect != null) {
System.out.println("type = " + ueSelect.type());
}
System.out.println("Inject is ambiguous? " + instance.isAmbiguous());
if (instance.isUnsatisfied()) {
System.out.println("Inject is unsatified!");
return null;
}
// this would be ok, but causes an exception
return instance.select(ueSelect).get();
// or rather this:
for (Iterator<UEInterface> it = instance.iterator(); it.hasNext();) {
// problem: calling next() will trigger instantiation which will call this method again :(
UEInterface candidate = it.next();
System.out.println(candidate.getClass().getName());
}
}
This code is close to an example I've seen: The #Produces method will be used to select and create instances and a list of candidates is injected as Instance<E>. If the method simply creates and returns an implementation, it works fine. I just don't know how to examine and select a candidate from the Instance<E>. The only way of looking the the "contents" seems to be an Iterator<E>. But as soon as I call next(), it will try to create the implementation... and unfortunately, calls my #Produces method for that, thereby creating an infinite recursion. What am I missing? How can I inspect the candidates and select one? Of course I want to instantiate only one of them...
Thanks in advance for any help and hints!
I think the issue is you are trying to select the annotation's class rather than using the annotation as a selector qualifier. Using the class directly searches for an implementation that implements that class. You need to create an AnnotationLiteral using the #ImplDescriptor class to perform a select using it as a qualifier. Create a class extending AnnotationLiteral like so.
public class ImplDescriptorLiteral extends AnnotationLiteral<ImplDescriptor> implements ImplDescriptor {
private String type;
public ImplDescriptorLiteral(String type) {
this.type = type;
}
#Override
public String type() {
return type;
}
}
then you can pass an instance of this class to the select method using the type you want.
instance.select(new ImplDescriptorLiteral("type1")).get();
Refer to the Obtaining a contextual instance by programmatic lookup documentation for more information.
Finch, what you have here should work. it assumes though that you have an instance of UEInterface that is annotated #UESelect, e.g.
#UESelect("one")
public class UEOne implements UEInterface {
..
}
Is this how you're expecting it to work?
I am writing my first Annotations processor and having trouble with something that seems trivial but I cannot find any information about it.
I have a element annotated with my annotation
#MyAnnotation String property;
When I get this property as a element in my processor I can not seem to get the type of the element in any way. In this case a would want to get a Class or TypeElement instance representing String.
I tried instantiating a class object of the container type with Class.forName() but it threw a ClassNotFoundException. I think this is because I do not have access to the class loader containing the class?
When running your annotation processor, you don't have access to the compiled classes. The point of annotation processing is that it happens pre-compile.
Instead, you need to create an annotation processor that specifically handles your annotation type, then use the mirror API to access the field. For example:
#SupportedAnnotationTypes("com.example.MyAnnotation")
public class CompileTimeAnnotationProcessor extends AbstractProcessor {
#Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
// Only one annotation, so just use annotations.iterator().next();
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(
annotations.iterator().next());
Set<VariableElement> fields = ElementFilter.fieldsIn(elements);
for (VariableElement field : fields) {
TypeMirror fieldType = field.asType();
String fullTypeClassName = fieldType.toString();
// Validate fullTypeClassName
}
return true;
}
}
For the validation, you cannot use any classes which have yet to be compiled (including those that are about to be compiled with the annotation) using something like MyType.class. For these, you must use strings only. That is because annotation processing occurs during a pre-compiling phase known as "source generation", which is what allows you to generate source code before the compiler runs using annotations.
An example validation verifying that the field type is java.lang.String (which is already compiled):
for (VariableElement field : fields) {
TypeMirror fieldType = field.asType();
String fullTypeClassName = fieldType.toString();
if (!String.class.getName().equals(fullTypeClassName)) {
processingEnv.getMessager().printMessage(
Kind.ERROR, "Field type must be java.lang.String", field);
}
}
Resources
Main APT Page
Mirror API Javadocs (Java 7 and older)
Edit: Mirror API Javadocs (Java 8)
Note that the mirror API is now standardized in Java 8 under javax.lang.model and the old API is deprecated. See this blog post for more information. If you've been using the javax classes, then you don't need to worry.
Edit:
I want to get the field type to get annotations on that type. But this does not seem like it will be possible?
Indeed it is possible! This can be done using more methods on the TypeMirror:
if (fieldType.getKind() != TypeKind.DECLARED) {
processingEnv.getMessager().printMessage(
Kind.ERROR, "Field cannot be a generic type.", field);
}
DeclaredType declaredFieldType = (DeclaredType) fieldType;
TypeElement fieldTypeElement = (TypeElement) declaredFieldType.asElement();
From here, you have two choices:
If the annotation you're trying to find is already compiled (i.e. it's from another library) then you can reference the class directly to get the annotation.
If the annotation you're trying to find is not compiled (i.e. it's being compiled in the current call to javac that's running the APT) then you can reference it via AnnotationMirror instances.
Already Compiled
DifferentAnnotation diffAnn = fieldTypeElement.getAnnotation(
DifferentAnnotation.class);
// Process diffAnn
Very straight-forward, this gives you direct access to the annotation itself.
Not Compiled
Note that this solution will work regardless of whether or not the annotation is compiled, it's just not as clean as the code above.
Here are a couple methods I wrote once to extract a certain value from an annotation mirror by its class name:
private static <T> T findAnnotationValue(Element element, String annotationClass,
String valueName, Class<T> expectedType) {
T ret = null;
for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
DeclaredType annotationType = annotationMirror.getAnnotationType();
TypeElement annotationElement = (TypeElement) annotationType
.asElement();
if (annotationElement.getQualifiedName().contentEquals(
annotationClass)) {
ret = extractValue(annotationMirror, valueName, expectedType);
break;
}
}
return ret;
}
private static <T> T extractValue(AnnotationMirror annotationMirror,
String valueName, Class<T> expectedType) {
Map<ExecutableElement, AnnotationValue> elementValues = new HashMap<ExecutableElement, AnnotationValue>(
annotationMirror.getElementValues());
for (Entry<ExecutableElement, AnnotationValue> entry : elementValues
.entrySet()) {
if (entry.getKey().getSimpleName().contentEquals(valueName)) {
Object value = entry.getValue().getValue();
return expectedType.cast(value);
}
}
return null;
}
Let's say that you're looking for the DifferentAnnotation annotation and your source code looks like this:
#DifferentAnnotation(name = "My Class")
public class MyClass {
#MyAnnotation
private String field;
// ...
}
This code will print My Class:
String diffAnnotationName = findAnnotationValue(fieldTypeElement,
"com.example.DifferentAnnotation", "name", String.class);
System.out.println(diffAnnotationName);