PersistenceConstructor argument variable name doesn't match instance variable name - java

I'm trying to persist the following object with spring-data-mongodb version 1.1.1.RELEASE:
#Document
public static class TestObject {
private final int m_property;
#PersistenceConstructor
public TestObject(int a_property) {
m_property = a_property;
}
public int property() {
return m_property;
}
}
I get a MappingException when I try to read the object back from the database (see full stacktrace below)
The naming convention my group uses requires argument variable names to be prefaced by a_ and instance variable names to be prefaced by m_. It seems like spring-data-mongodb is making the assumption that the constructor argument variable names must match the object instance variable names.
Why doesn't spring-data-mongodb use the constructor argument to instance variable mapping that I define within the constructor?
Is there another way to define this mapping such that spring-data-mongodb will properly construct my object, or is my only option to break the naming convention?
.
Exception in thread "main" org.springframework.data.mapping.model.MappingException: No property a_property found on entity class com.recorder.TestRecorder$TestObject to bind constructor parameter to!
at org.springframework.data.mapping.model.PersistentEntityParameterValueProvider.getParameterValue(PersistentEntityParameterValueProvider.java:90)
at org.springframework.data.convert.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:70)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:229)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:209)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:173)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:169)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:72)
at org.springframework.data.mongodb.core.MongoTemplate$ReadDbObjectCallback.doWith(MongoTemplate.java:1820)
at org.springframework.data.mongodb.core.MongoTemplate.executeFindMultiInternal(MongoTemplate.java:1542)
at org.springframework.data.mongodb.core.MongoTemplate.findAll(MongoTemplate.java:1064)
at com.recorder.TestRecorder.main(TestRecorder.java:43)

tl;dr
We need to rely on constructor argument names to match field names to find out which field of the document to pull in. If you want to customize this use #Value("#root.field_name") on the constructor argument.
Long story
If you're using a constructor with arguments to let Spring Data instantiate the given class using this constructor we have to hand parameters to the constructor upon invocation. To find out which document field we have to hand in, we need to inspect the matching property for potential field name customization. See the following example:
#Document
class MyEntity {
#Field("foo")
private String myField;
public MyEntity(String myField) {
this.myField = myField;
}
}
In this case we need to pipe the field foo into the constructor and there's no way to find out about this if we don't somehow can obtain a reference to the property. If the constructor parameter name was something different, how should we reliably find out which field value should actually be used as argument? The example you've shown in your question can never work out of the box, as your document would contain a m_property field and there's absolutely no way to find out you actually want that to be injected, except adding more explicit configuration.
To customize this behavior you can use Spring's #Value annotation and inject a custom document field into the constructor. The document itself is available through the #root variable. So you could easily alter my sample above to:
#Document
class MyEntity {
#Field("foo")
private String myField;
public MyEntity(#Value("#root.foo") String somethingDifferent) {
this.myField = somethingDifferent;
}
}
I'd strongly recommend that you add custom field names to your properties as well as you don't want to expose your property naming conventions to the database. The usage pf #Value is briefly mentioned in the reference docs but I've created a ticket to improve the docs and make this more obvious.

You can use some custom converters (and remove #PersistenceConstructor):
// DB => Java
package com.recorder.converters;
public class TestObjectReadConverter implements Converter<DBObject, TestObject>
{
public TestObject convert(final DBObject source) {
return new TestObject((Integer) source.get("m_property"));
}
}
.
// JAVA => DB
package com.recorder.converters;
public class TestObjectWriteConverter implements Converter<TestObject, DBObject>
{
public DBObject convert(final TestObject source) {
return new BasicDBObjectBuilder("m_property", source.property()).get();
}
}
Don't forget to declare those (xml config):
<mongo:mapping-converter base-package="com.recorder">
<mongo:custom-converters>
<mongo:converter>
<bean class="com.recorder.converters.TestObjectReadConverter" />
</mongo:converter>
<mongo:converter>
<bean class="com.recorder.converters.TestObjectWriteConverter"/>
</mongo:converter>
</mongo:custom-converters>
</mongo:mapping-converter>
see this reference
Side note: this is a work around, I don't think naming convention are meant to be so tight that you need to work around. Perhaps it's time for your group to "rethink" those naming convention (for productivity sake in that case).

Related

Java custom validation annotation

public class Student{
#NotNull
private Course course= null;
#CustomValidation(enumCourse = course)
private String details = null;
}
}
How can i pass the course variable to CustomValidation annotation? Im getting an error saying that course must be an enum constant expression.
I have written a custom validation interface and validator too.
Annotation property must be constant at compile time.
You cannot use variable there.
The keyword here is cross fields validation.
You have two option:
Create annotation at class level. There you have access to all properties of class and validation should be done easy
Or create annotation at method level which return all necessary fields for validations.
#CustomAnnotations
Pair<Course, String> getCourseAndDetailForValidation() {
return Pair.of(course, details)
}
You can change return type to match your taste, it may be a List, an Array, wrapper objects...
It's specified by section 9.6.1 of the JLS. The annotation member types must be one of:
primitive
String
Class
an Enum
another Annotation
an array of any of the above
Course must be one of those types.

Java type level annotation and private member visibility

I'm trying to externalize the baseurl of a spring #Controller into a static String member of the controller class. If i do so i have to declare the member public - otherwise it will not be visible to the #RequestMapping annotation. I can't understand why - isn't the annotation part of the class?
Can someone explain why i am forced not to use private here?
Invalid:
#Controller
#RequestMapping(PendingApprovalsController.CONTROLLER_URL)
public class PendingApprovalsController {
private static final String CONTROLLER_URL = "/some/url";
...
}
Valid:
#Controller
#RequestMapping(PendingApprovalsController.CONTROLLER_URL)
public class PendingApprovalsController {
public static final String CONTROLLER_URL = "/some/url";
...
}
An annotation is an ordinary Java class itself. It can't see the members of an annotated class unless they are visible to it, using normal Java rules. Furthermore the existence of annotations may be used by other code at runtime to operate on the members of the annotated class; if those members are not visible, those operations can't be performed.
This page from the JDK documentation package talks about how to define custom annotations, and how to consume annotations applied in code, and it's enlightening in this regard. To define an annotation type, you do something like
public #interface Copyright {
String value();
}
Your custom annotation is defined as a special kind of Java interface; in user, the JVM supplies a runtime implementation as needed. But the key thing to note is how you use your custom annotation, for example:
Method m = ... // Get a java.lang.reflect.Method object from somewhere
if (m.hasAnnotation(Copyright.class)) ...
Note that here we refer to the class object of the annotation type, demonstrating that an annotation is compiled to a normal Java type.

Using static variables in Spring annotations

I'm using spring's PreAuthorize annotation as follows:
#PreAuthorize("hasRole('role')");
However, I already have 'role' defined as a static String on another class. If I try to use this value:
#PreAuthorize("hasRole(OtherClass.ROLE)");
I get an error:
org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 14): Field or property 'OtherClass' cannot be found on object of type 'org.springframework.security.access.expression.method.MethodSecurityExpressionRoot'
Is there a way to access static variables like this with a PreAuthorize annotation?
Try the following which uses Spring Expression Language to evaluate the type:
#PreAuthorize("hasRole(T(fully.qualified.OtherClass).ROLE)");
Be sure to specify the fully qualified class name.
Documentation
You can also create a bean container with roles, like:
#Component("R")
public final class RoleContainer {
public static final String ROLE_A = "ROLE_A";
}
then on controller you can use:
#PreAuthorize("hasRole(#R.ROLE_A)")
To make it possible to write expressions without package names:
<sec:global-method-security>
<sec:expression-handler ref="methodSecurityExpressionHandler"/>
</sec:global-method-security>
<bean id="methodSecurityExpressionHandler" class="my.example.DefaultMethodSecurityExpressionHandler"/>
Then extend the DefaultMethodSecurityExpressionHandler:
public class DefaultMethodSecurityExpressionHandler extends org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler {
#Override
public StandardEvaluationContext createEvaluationContextInternal(final Authentication auth, final MethodInvocation mi) {
StandardEvaluationContext standardEvaluationContext = super.createEvaluationContextInternal(auth, mi);
((StandardTypeLocator) standardEvaluationContext.getTypeLocator()).registerImport("my.example");
return standardEvaluationContext;
}
}
Now create my.example.Roles.java :
public class Roles {
public static final String ROLE_UNAUTHENTICATED = "ROLE_UNAUTHENTICATED";
public static final String ROLE_AUTHENTICATED = "ROLE_AUTHENTICATED";
}
And refer to it without package name in annotations:
#PreAuthorize("hasRole(T(Roles).ROLE_AUTHENTICATED)")
instead of:
#PreAuthorize("hasRole(T(my.example.Roles).ROLE_AUTHENTICATED)")
Makes it more readable imho. Also roles are now typed. Write:
#PreAuthorize("hasRole(T(Roles).ROLE_AUTHENTICATEDDDD)")
and you will get startup errors that wouldn't have been there if you wrote:
#PreAuthorize("hasRole('ROLE_AUTHENTICATEDDDD')")
Try something like this:
#PreAuthorize("hasRole(T(com.company.enumpackage.OtherClass).ROLE.name())");
If your OtherClass enum is declared as public static, then you need to use $ sign:
#PreAuthorize("hasRole(T(com.company.ParentTopLevelClass$OtherClass).ROLE.name())");
name() to prevent futer problems if toString() will be overriden later
The accepted answer from Kevin Bowersox works, but I didn't like having the T(fully.qualified.path) stuff so I kept looking. I started by creating a custom security method using the answer from James Watkins here:
How to create custom methods for use in spring security expression language annotations
However, instead of a String, I used my enums.Permissions class as the parameter type:
#Component
public class MySecurityService {
public boolean hasPermission(enums.Permissions permission) {
...do some work here...
return true;
}
}
Now the neat part is that when I call the hasPermission from an annotation, I don't have to have to type the whole path, but I do have to enclose it in single quotes:
#PreAuthorize("#mySecurityService.hasPermission('SOME_ROLE_NAME')")
Because the hasPermission method expects an Enum, it will automatically find the Enum value with that name. If it doesn't find it you'll get an exception:
org.springframework.expression.spel.SpelEvaluationException: Type conversion problem, cannot convert from java.lang.String to enums.Permissions
You can rename hasPermission to hasRole, in which case the only trade off is that you are trading T(fully.qualified.path) for #mySecurityService and extra single quotes.
Not sure if it is any better, but there it is. Since none of this is going to verify the values at compile time anyways, my next step is to make an annotation processor.
I also have to give credit to krosenvold for pointing out that spring can automatically convert to an enum:
https://stackoverflow.com/a/516899/618881

Java Annotations values provided in dynamic manner

I want to provide annotations with some values generated by some methods.
I tried this so far:
public #interface MyInterface {
String aString();
}
#MyInterface(aString = MyClass.GENERIC_GENERATED_NAME)
public class MyClass {
static final String GENERIC_GENERATED_NAME = MyClass.generateName(MyClass.class);
public static final String generateName(final Class<?> c) {
return c.getClass().getName();
}
}
Thought GENERIC_GENERATED_NAME is static final, it complains that
The value for annotation attribute MyInterface.aString must be a constant expression
So how to achieve this ?
There is no way to dynamically generate a string used in an annotation. The compiler evaluates annotation metadata for RetentionPolicy.RUNTIME annotations at compile time, but GENERIC_GENERATED_NAME isn't known until runtime. And you can't use generated values for annotations that are RetentionPolicy.SOURCE because they are discarded after compile time, so those generated values would never be known.
The solution is to use an annotated method instead. Call that method (with reflection) to get the dynamic value.
From the user's perspective we'd have:
#MyInterface
public class MyClass {
#MyName
public String generateName() {
return MyClass.class.getName();
}
}
The annotation itself would be defined as
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface #MyName {
}
Implementing the lookup for both of these annotations is rather straight-forward.
// as looked up by #MyInterface
Class<?> clazz;
Method[] methods = clazz.getDeclaredMethods();
if (methods.length != 1) {
// error
}
Method method = methods[0];
if (!method.isAnnotationPresent(MyName.class)) {
// error as well
}
// This works if the class has a public empty constructor
// (otherwise, get constructor & use setAccessible(true))
Object instance = clazz.newInstance();
// the dynamic value is here:
String name = (String) method.invoke(instance);
There is no way to modify the properties of an annotation dynamically like others said. Still if you want to achieve that, there are two ways to do this.
Assign an expression to the property in the annotation and process that expression whenever you retrieve the annotation. In your case your annotation can be
#MyInterface(aString = "objectA.doSomething(args1, args2)")
When you read that, you can process the string and make the method invocation and retrieve the value. Spring does that by SPEL (Spring expression language). This is resource intensive and the cpu cycles are wasted every time we want to process the expression. If you are using spring, you can hook in a beanPostProcessor and process the expression once and store the result somewhere. (Either a global properties object or in a map which can be retrieved anywhere).
This is a hacky way of doing what we want. Java stores a private variable which maintains a map of annotations on the class/field/method. You can use reflection and get hold of that map. So while processing the annotation for the first time, we resolve the expression and find the actual value. Then we create an annotation object of the required type. We can put the newly created annotation with the actual value (which is constant) on the property of the annotation and override the actual annotation in the retrieved map.
The way jdk stores the annotation map is java version dependent and is not reliable since it is not exposed for use (it is private).
You can find a reference implementation here.
https://rationaleemotions.wordpress.com/2016/05/27/changing-annotation-values-at-runtime/
P.S: I haven't tried and tested the second method.

Using Property Type Info Hibernate NamingStrategy

I am writing a Hibernate NamingStrategy for our schema.
Our legacy schema (for some reason!) includes type information in column names. For example:
strproperty
intcustomer
I am implementing the following method from the NamingStrategy interface:
public String propertyToColumnName(String propertyName)
I would like to know the Java type of the property above, so that I could implement our naming strategy such that:
public String getProperty() {
return this.property()
}
would map to strproperty automatically.
For arguments sake say this is the way I would envisage it working (I know this method does not exist):
public String propertyToColumnName(Object propertyName, Class propertyType) {
if (propertyType instanceof String) {
return "str" + propertyName;
} else {
return "int" + propertyName;
}
}
Is there any way to ascertain the type of the Java property from within the naming convention hooks in Hibernate?
Thanks in advance.
I don't think the interface is designed with that in mind - at first glance it doesn't seem like there's any way to know what class the caller of propertyToColumnName() is enquiring about
However, I note that the javadocs for propertyToColumnName() say that it returns "...a column name for a property path expression". It might be worth investigating what String gets passed in to this method by Hibernate: if it is actually fully qualified with the class name then you could parse out the class name and use reflection on that class to determine the property type.
Alternatively, don't forget you can always specify column names directly in your mapping files (or in your annotations if you use them, I guess):
<property name="blah" column="intblah"/>
which might be a simpler approach.

Categories