I have the typical example, where a POST has many TAGS, and a TAG has many POSTs.
Instead of using a typical #ManyToMany, I use a domain object in the middle, called TAGPOST, which also allows me to have useful data in there, such as when a post was tagged with a given tag, etc. Each POST, and TAG resp, is in a #OneToMany relationship with a TAGPOST.
The specific requirement is that a post cannot have the same tag included twice, therefore the TAGPOST.post and TAGPOST.tag pair must always be unique. Normally, I would do that by making a composite primary key pair in the table, responsible for storing TAGPOST objects.
AFAIK, there is no way to express this unique constraint. I have marked jpa.ddl=update, which means that every time I move the application to a new environment, I will have to go and manually fix this in the DB. This is very inconvenient, and error prone, especially when unit testing, because then the database is created and dropped more or less in every iteration.
I was even thinking to do the check manually on #PrePersist, or even move the check in a business layer, say, create a PostService.
What do I do? Am I missing something that Play has by default? Some clever annotation to express the uniqueness of the #ManyToOne properties of the TAGPOST class?
FYI: I am using Play 1.2.5
EDIT: The TAGPOST class looks like this:
#Entity
public class TagPost extends Model {
#ManyToOne
public Tag tag;
#ManyToOne
public Post post;
public Date dateAdded;
...
}
I wrote a custom Check for db uniqueness. Maybe you should customize it.
DBUnique.java
package models.check;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import net.sf.oval.configuration.annotation.Constraint;
import play.db.jpa.GenericModel;
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.PARAMETER })
#Constraint(checkWith = DbUniqueCheck.class)
public #interface DBUnique {
String message() default DbUniqueCheck.mes;
Class<? extends GenericModel> modelClass();
String field() default ""; // field name will be used
}
DbUniqueCheck.java
package models.check;
import net.sf.oval.Validator;
import net.sf.oval.configuration.annotation.AbstractAnnotationCheck;
import net.sf.oval.context.FieldContext;
import net.sf.oval.context.OValContext;
import org.apache.commons.lang.StringUtils;
import play.db.jpa.GenericModel.JPAQuery;
#SuppressWarnings("serial")
public class DbUniqueCheck extends AbstractAnnotationCheck<DBUnique> {
final static String mes = "validation.dbunique";
DBUnique dbUnique;
#Override
public void configure(DBUnique dBUnique) {
this.dbUnique = dBUnique;
setMessage(dBUnique.message());
}
public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) {
try {
String field = dbUnique.field();
if (field == null || field.isEmpty()) {
field = ((FieldContext) context).getField().getName();
}
JPAQuery q = (JPAQuery) dbUnique.modelClass().getMethod("find", String.class, Object[].class)
.invoke(null, "by" + StringUtils.capitalize(field), new Object[] { value });
return q.first() == null;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
usage : link to gist
it simply checks the given field for given class instance is unique in db. Maybe you should make something like these..
Related
I have a service method:
#GetMapping(path = "/api/some/path", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> getWhatever(#RequestParam(value = "page-number", defaultValue = "0") #Min(0) Integer pageNumber, ...
If the caller of an API doesn't submit a proper value for page-number query parameter, javax.ConstraintViolationexception is being raised. The message of the exception would read smth like:
getWhatever.pageNumber must be equal or greater than 0
In the response body, I would like to have this message instead:
page-number must be equal or greater than 0
I want my message to have the name of a query parameter, not the name of the argument. IMHO, including the name of the argument is exposing the implementation details.
The problem is, I cannot find an object that is carrying query parameter name. Seems like the ConstraintViolationException doesn't have it.
I am running my app in spring-boot.
Any help would be appreciated.
P.S.: I have been to the other similar threads that claim to solve the problem, none of them actually do in reality.
Here is how I made it work in spring-boot 2.0.3:
I had to override and disable ValidationAutoConfiguration in spring-boot:
import org.springframework.boot.validation.MessageInterpolatorFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import javax.validation.Validator;
#Configuration
public class ValidationConfiguration {
public ValidationConfiguration() {
}
#Bean
public static LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setParameterNameDiscoverer(new CustomParamNamesDiscoverer());
MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
return factoryBean;
}
#Bean
public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment, #Lazy Validator validator) {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
boolean proxyTargetClass = (Boolean) environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
processor.setProxyTargetClass(proxyTargetClass);
processor.setValidator(validator);
return processor;
}
}
CustomParamNamesDiscoverer sits in the same package and it is a pretty much a copy-paste of DefaultParameterNameDiscoverer, spring-boot's default implementation of param name discoverer:
import org.springframework.core.*;
import org.springframework.util.ClassUtils;
public class CustomParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
private static final boolean kotlinPresent = ClassUtils.isPresent("kotlin.Unit", CustomParameterNameDiscoverer.class.getClassLoader());
public CustomParameterNameDiscoverer() {
if (kotlinPresent) {
this.addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
}
this.addDiscoverer(new ReqParamNamesDiscoverer());
this.addDiscoverer(new StandardReflectionParameterNameDiscoverer());
this.addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
}
}
I wanted it to remain pretty much intact (you can see even kotlin checks in there) with the only addition:
I am adding an instance of ReqParamNamesDiscoverer to the linked lists of discoverers. Note that the order of addition does matter here.
Here is the source code:
import com.google.common.base.Strings;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.RequestParam;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class ReqParamNamesDiscoverer implements ParameterNameDiscoverer {
public ReqParamNamesDiscoverer() {
}
#Override
#Nullable
public String[] getParameterNames(Method method) {
return doGetParameterNames(method);
}
#Override
#Nullable
public String[] getParameterNames(Constructor<?> constructor) {
return doGetParameterNames(constructor);
}
#Nullable
private static String[] doGetParameterNames(Executable executable) {
Parameter[] parameters = executable.getParameters();
String[] parameterNames = new String[parameters.length];
for (int i = 0; i < parameters.length; ++i) {
Parameter param = parameters[i];
if (!param.isNamePresent()) {
return null;
}
String paramName = param.getName();
if (param.isAnnotationPresent(RequestParam.class)) {
RequestParam requestParamAnnotation = param.getAnnotation(RequestParam.class);
if (!Strings.isNullOrEmpty(requestParamAnnotation.value())) {
paramName = requestParamAnnotation.value();
}
}
parameterNames[i] = paramName;
}
return parameterNames;
}
}
If parameter is annotated with RequestParam annotation, I am retrieving the value attribute and return it as a parameter name.
The next thing was disabling auto validation config, somehow, it doesn't work without it. This annotation does the trick though:
#SpringBootApplication(exclude = {ValidationAutoConfiguration.class})
Also, you need to have a custom handler for your ConstraintValidationException :
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(ConstraintViolationException.class)
public ErrorDTO handleConstraintViolationException(ConstraintViolationException ex) {
Map<String, Collection<String>> errors = new LinkedHashMap<>();
ex.getConstraintViolations().forEach(constraintViolation -> {
String queryParamPath = constraintViolation.getPropertyPath().toString();
log.debug("queryParamPath = {}", queryParamPath);
String queryParam = queryParamPath.contains(".") ?
queryParamPath.substring(queryParamPath.indexOf(".") + 1) :
queryParamPath;
String errorMessage = constraintViolation.getMessage();
Collection<String> perQueryParamErrors = errors.getOrDefault(queryParam, new ArrayList<>());
perQueryParamErrors.add(errorMessage);
errors.put(queryParam, perQueryParamErrors);
});
return validationException(new ValidationException("queryParameter", errors));
}
ValidationException stuff is my custom way of dealing with validation errors, in a nutshell, it produces an error DTO, which will be serialized into JSON with all the validation error messages.
Add a custom message to #Min annotation like this
#Min(value=0, message="page-number must be equal or greater than {value}")
Right now, you cannot do it (well, except if you define a custom message for each annotation but I suppose that's not what you want).
Funnily enough, someone worked recently on something very similar: https://github.com/hibernate/hibernate-validator/pull/1029 .
This work has been merged to the master branch but I haven't released a new 6.1 alpha containing this work yet. It's a matter of days.
That being said, we had properties in mind and now that you ask that, we should probably generalize that to more things, method parameters included.
Now that we have the general idea, it shouldn't be too much work to generalize it, I think.
I'll discuss this with the contributor and the rest of the team and get back to you.
I don't think getting the name of the query parameter is possible but would like to be proven wrong if somebody knows a way.
As Dmitry Bogdanovich says, having a custom message is the easiest and only way I know how to do something close to what you need. If you say you don't want to clutter your code with these messages, you can just do this:
Add a ValidationMessages.properties file in your resources folder. Here you can just say:
page_number.min=page-number must be equal or greater than {value}
Now you can use the annotation and write:
#Min(value = 0, message = "{page_number.min}")
This way you have a single source to change anything about the message when needed.
I am working on a project that I need to put some limitations/constrains on the fields of the models(e.g. "String name" field should not exceed 10 characters) . I can only find Java Bean Validation API for this job. However as I see, it is used with Hibernate and Spring Framework.
Unfortunately, an ORM like Hibernate was not used in the project. We are using DAO pattern and JDBI for database operations.
Is there any alternative annotations on Java which helps to put constrains on the fields like Bean Validation does( and hopefully works with magic like Lombok does)? I need basically Size, Min/Max and NonNull annotations.
Basically something like this:
class User {
#Size(max = 10)
String name;
}
karelss already answered, you can also use javax.validation.constraints package here maven link. Here is possible implementation and test code (not perfect one).
User.java
import javax.validation.constraints.Size;
class User {
#Size(max = 10)
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
UserTest.java
import static org.junit.Assert.assertEquals;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.junit.Test;
public class UserTest {
#Test
public void test() {
User user = new User();
// set name over 10
user.setName("12345678912");
// validate the input
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<User>> violations = validator.validate(user);
for (ConstraintViolation v : violations) {
String key = "";
if (v.getPropertyPath() != null) {
key = v.getPropertyPath().toString();
assertEquals("name", key);
assertEquals("size must be between 0 and 10", v.getMessage());
}
}
assertEquals(1, violations.size());
}
}
Java Bean Validation API is the right tool for this job, but as you say is an api, if you are using an application server, you will have different implementations and you can use whatever you want, it's not linked to hibernate or spring, what you see are different providers of the api implementatión. This api works with objects, you can annotate any object with it.
If you don't want to include dependencies you can implement this validations in a compatible way using your own annotations like here
Java 7 Bean validation API
I have a very basic JAX-RS service (the BookService class below) which allows for the creation of entities of type Book (also below). POSTing the payload
{
"acquisitionDate": 1418849700000,
"name": "Funny Title",
"numberOfPages": 100
}
successfully persists the Book and returns 201 CREATED. However, including an id attribute with whichever non-null value on the payload triggers an org.hibernate.PersistentObjectException with the message detached entity passed to persist. I understand what this means, and including an id on the payload when creating an object (in this case) makes no sense. However, I'd prefer to prevent this exception from bubbling all the way up and present my users with, for instance, a 400 BAD REQUEST in this case (or, at least, ignore the attribute altogether). However, there are two main concerns:
The exception that arrives at create is an EJBTransactionRolledbackException and I'd have to crawl all the way down the stack trace to discover the root cause;
The root cause is org.hibernate.PersistentObjectException - I'm deploying to Wildfly which uses Hibernate, but I want to maintain my code portable, so I don't really want to catch this specific exception.
To my understanding, there are two possible solutions:
Use book.setId(null) before bookRepo.create(book). This would ignore the fact that the id attribute carries a value and proceed with the request.
Check if book.getId() != null and throw something like IllegalArgumentException that could be mapped to a 400 status code. Seems the preferable solution.
However, coming from other frameworks (like Django Rest Framework, for example) I'd really prefer this to be handled by the framework itself... My question then is, is there any built-in way to achieve this behaviour that I may be missing?
This is the BookService class:
#Stateless
#Path("/books")
public class BookService {
#Inject
private BookRepo bookRepo;
#Context
UriInfo uriInfo;
#Consumes(MediaType.APPLICATION_JSON)
#Path("/")
#POST
#Produces(MediaType.APPLICATION_JSON)
public Response create(#Valid Book book) {
bookRepo.create(book);
return Response.created(getBookUri(book)).build();
}
private URI getBookUri(Book book) {
return uriInfo.getAbsolutePathBuilder()
.path(book.getId().toString()).build();
}
}
This is the Book class:
#Entity
#Table(name = "books")
public class Book {
#Column(nullable = false)
#NotNull
#Temporal(TemporalType.TIMESTAMP)
private Date acquisitionDate;
#Column(nullable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
private Integer id;
#Column(nullable = false)
#NotNull
#Size(max = 255, min = 1)
private String name;
#Column(nullable = false)
#Min(value = 1)
#NotNull
private Integer numberOfPages;
(getters/setters/...)
}
This is the BookRepo class:
#Stateless
public class BookRepo {
#PersistenceContext(unitName = "book-repo")
protected EntityManager em;
public void create(Book book) {
em.persist(book);
}
}
I don't know if this is really the answer you're looking for, but I was just playing around with the idea and implemented something.
The JAX-RS 2 spec defines a model for bean validation, so I thought maybe you could tap into that. All bad validations will get mapped to a 400. You stated "I'd prefer to prevent this exception from bubbling all the way up and present my users with, for instance, a 400 BAD REQUEST", but with bad validation you will get that anyway. So however you plan to handle validation exceptions (if at all), you can do the same here.
Basically I just created a constraint annotation to validate for a null value in the id field. You can define the id field's name in the annotation through the idField annotation attribute, so you are not restricted to id. Also this can be used for other objects too, so you don't have to repeatedly check the value, as you suggested in your second solution.
You can play around with it. Just thought I'd throw this option out there.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
#Constraint(validatedBy = NoId.NoIdValidator.class)
#Target({ElementType.PARAMETER})
#Retention(RUNTIME)
public #interface NoId {
String message() default "Cannot have value for id attribute";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String idField() default "id";
public static class NoIdValidator implements ConstraintValidator<NoId, Object> {
private String idField;
#Override
public void initialize(NoId annotation) {
idField = annotation.idField();
}
#Override
public boolean isValid(Object bean, ConstraintValidatorContext cvc) {
boolean isValid = false;
try {
Field field = bean.getClass().getDeclaredField(idField);
if (field == null) {
isValid = true;
} else {
field.setAccessible(true);
Object value = field.get(bean);
if (value == null) {
isValid = true;
}
}
} catch (NoSuchFieldException
| SecurityException
| IllegalArgumentException
| IllegalAccessException ex) {
Logger.getLogger(NoId.class.getName()).log(Level.SEVERE, null, ex);
}
return isValid;
}
}
}
Usage:
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response createBook(#Valid #NoId(idField = "id") Book book) {
book.setId(1);
return Response.created(URI.create("http://blah.com/books/1"))
.entity(book).build();
}
Note the default idField is id, so if you don't specify it, it will look for the id field in the object class. You can also specify the message as you would any other constraint annotation:
#NoId(idField = "bookId", message = "bookId must not be specified")
// default "Cannot have value for id attribute"
My problem is related to localization, I have JPA entities having fields like fieldnameEN,fieldnameFR,fieldnameRU. I want to write some getter method which automatically detects current locale and returns appropriate field. I'm using facelets and such getter will help me to call getter and delegate localization issues to backend. Locale already stored in session and getting it is not a problem. I know how do such method manually, something like
getProperty(){
locale = takeFromSession();
if(locale=en) return getPropertyEN();
if(locale=fr) return getPropertyFR();
}
But I want to keep DRY principle with the help of AspectJ or some interceptor.
Current thoughts on implementation - in each getter call determine running method name, pass Object state to some interceptor and in interceptor perform locale checks and return appropriate value of field which is already passed to interceptor.
Is there any working example of solving such problem?
How pass object state to interceptor?
Are there any better approaches to solve my problem?
UPDATE
Kriegaex recommended to use bundles. Actually, we use bundles for markup (headers and captions) but we also need to localize entities stored in database. Bundles require usage of 'hash tags' as keys in .property files, but I don't want to store entity values as hash tags. Bundles will force users to fill business values as 'hash tags' and I wouldn't like that)
Even if use english values as key or some hash from values we need to make 100 queries if entity has 100 properties. And yes, I mean 100 DB queries, because AFAIK bundle stored in RAM which may be insufficient to store translations that's why in our case bundles should be in key-value DB.
About recompilation - most probably we will have only 3 languages and don't need scale in such direction.
If somebody know answer to my topic question, please share some little example)
AspectJ is not intended to be used to patch up bad application design. I could easily tell you how to write some cheap aspect code using reflection in order to call the right getter for the current language, but
it is ugly,
it is slow,
having one property + getter per language and encoding the language ID in the method name does not scale. If you want to add another language, you will have to add fields to dozens or hundreds of entities.
Maybe you should consider using a standard means like resource bundles for your property names. This way you can change text constants or even add new languages without recompiling the code. Because internationalisation is a cross-cutting concern, you can then still use AspectJ in order to declare access methods for your translations via ITD (inter-type definition) or by some other means, if you want to keep them out of the core code. That way your core code could be totally language-agnostic.
Update:
Anyway, if you want it so much, here is a sample showing you what you can do with AOP, namely with AspectJ. The solution proposed by user gknicker is similar, but it only works for one class. Mine keeps the code separate in an aspect and can apply it to many classes at once.
The plan is to manually annotate each entity class containing multi-language field captions with a marker annotation. I made up one called #Entity. Alternatively, you could also determine the target classes by their superclass or by a class or package name pattern, AspectJ is very powerful in this regard. As I said, it is just an example.
In the next step we will define an aspect which does the following:
Define an interface LocalisedCaption.
Define a few sample default methods using reflection magic in order to
get the localised caption for one field,
get all localised captions for all defined entity fields,
get a map of localised captions and field values for an entity instance.
Use ITD (inter-type declaration) in order to make all #Entity classes implement that interface and thus inherit its methods.
Last, but not least, we will use the new methods in from sample application.
package de.scrum_master.app;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Entity {}
package de.scrum_master.app;
#Entity
public class Person {
public static final String firstNameEN = "first name";
public static final String firstNameFR = "prénom";
public static final String firstNameRU = "и́мя";
public static final String lastNameEN = "last name";
public static final String lastNameFR = "nom de famille";
public static final String lastNameRU = "фами́лия";
private String firstName;
private String lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
#Override
public String toString() {
return "Person [firstName=" + firstName + ", lastName=" + lastName + "]";
}
}
package de.scrum_master.aspect;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import de.scrum_master.app.Application;
import de.scrum_master.app.Entity;
public aspect EntityCaptionLocaliser {
public interface LocalisedCaption {
String getCaption(String attributeName);
}
declare parents :
#Entity * implements LocalisedCaption;
public String LocalisedCaption.getCaption(String attributeName)
throws ReflectiveOperationException
{
String fieldName = attributeName + Application.locale;
Field field = getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (String) field.get(this);
}
public Map<String, String> LocalisedCaption.getAllCaptions()
throws ReflectiveOperationException
{
Map<String, String> captions = new HashMap<>();
for (Field field : getClass().getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()))
continue;
String attributeName = field.getName();
captions.put(attributeName, getCaption(attributeName));
}
return captions;
}
public Map<String, Object> LocalisedCaption.getCaptionValuePairs()
throws ReflectiveOperationException
{
Map<String, Object> captions = new HashMap<>();
for (Field field : getClass().getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()))
continue;
field.setAccessible(true);
String attributeName = field.getName();
captions.put(getCaption(attributeName), field.get(this));
}
return captions;
}
}
package de.scrum_master.app;
public class Application {
public static String locale = "EN";
public static void main(String[] args) throws Exception {
Person albert = new Person("Albert", "Einstein");
System.out.println("Showing localised captions for " + albert + ":");
locale = "EN";
System.out.println(albert.getAllCaptions());
System.out.println(albert.getCaptionValuePairs());
locale = "FR";
System.out.println(albert.getAllCaptions());
System.out.println(albert.getCaptionValuePairs());
locale = "RU";
System.out.println(albert.getAllCaptions());
System.out.println(albert.getCaptionValuePairs());
}
}
Console output for Application.main:
Showing localised captions for Person [firstName=Albert, lastName=Einstein]:
{lastName=last name, firstName=first name}
{first name=Albert, last name=Einstein}
{lastName=nom de famille, firstName=prénom}
{nom de famille=Einstein, prénom=Albert}
{lastName=фами́лия, firstName=и́мя}
{фами́лия=Einstein, и́мя=Albert}
Better to use standard Java i18n.
However, if you insist on pursuing your current design, here's an example using reflection.
public class Scratch
{
public static void main(String[] args)
{
System.out.println(new Scratch().getProperty());
}
String propertyEN = "want";
String propertyFR = "voulez";
public String getProperty()
{
return (String)getForLocale("property");
}
private Object getForLocale(String attributeName)
{
String fieldName = attributeName + getLocale();
try {
Field field = getClass().getDeclaredField(fieldName);
return field.get(this);
} catch (ReflectiveOperationException e) {
return e.getMessage();
}
}
private String getLocale()
{
return "EN"; // takeFromSession();
}
}
I am using the Play Framework in it's current version and my model classes extend play.db.jpa.JPABase.
Today I tried to make an often used type of query generic and define a static helper method to construct it.
I wrote the following:
import play.db.jpa.Model;
import play.libs.F;
public class GenericQueries {
public static <T extends Model> F.Option<T> firstOption(
Class<T> clazz,
String query,
Object... parameters){
final T queryResult = T.find(query,parameters).first();
return (queryResult == null) ?
F.Option.<T>None() :
F.Option.Some(queryResult);
}
}
However, I get the following error:
Execution exception
UnsupportedOperationException occured : Please annotate your JPA model with #javax.persistence.Entity annotation.
I debugged into the method, at runtime T seems to be correctly set to it's corresponding Model class. I even see the annotation.
I suspect some class enhancing voodoo by the play guys responsible for this, but I am not entirely sure.
Any ideas?
Update: added Model class as Requested
Here is a shortened Version of one of the Model classes I use.
package models;
import org.apache.commons.lang.builder.ToStringBuilder;
import play.data.validation.Required;
import play.db.jpa.Model;
import play.modules.search.Field;
import play.modules.search.Indexed;
import javax.persistence.Column;
import javax.persistence.Entity;
import java.util.Date;
#Entity #Indexed
public class FooUser extends Model {
#Required
public Date firstLogin;
#Field
#Required(message = "needs a username")
#Column(unique = false,updatable = true)
public String name;
#Field
public String description;
#Required
public boolean isAdmin;
#Override
public String toString(){
return new ToStringBuilder(this)
.append("name", name)
.append("admin", isAdmin)
.toString();
}
}
In Play entites should extend play.db.jpa.Model and use #Entity annotation (class level).
For what you say I understand that you are extending play.db.jpa.JPABase.
This may be the reason of the issue, as Play (as you point) dynamically enhances classes and it may be clashing with your inheritance.
EDIT: I tested the issue
The problem is that Play is not enhancing the object T. This means that the find method called id the one of GenericModel (parent of Model) whose implementation is to throw an exception with the message.
The enhancer seems to detect only the classes with #Entity.
Even the solution of mericano1 doesn't work, the enhancer doesn't pick it. So I feel you won't be able to use that method in Play.
The best way to do that is to use a base class that extends play.db.jpa.Model with just the static methods that will be shared by the subclasses.
Add the #Entity annotation to the base class and no class fields.
import play.db.jpa.Model;
import play.libs.F;
public class BaseModel extends Model {
public static <T extends Model> F.Option<T> firstOption(
Class<T> clazz,
String query,
Object... parameters){
final T queryResult = T.find(query,parameters).first();
return (queryResult == null) ?
F.Option.<T>None() :
F.Option.Some(queryResult);
}
}
And then
#Entity
public class FooUser extends BaseModel {
....
}