genericsinheritancegwtpolymorphismrequestfactory

RequestFactory client-side inheritance of typed class with generics


I would like to know, is it possible to use generic classes of request factory proxy/context for common actions for all entities, like getById(Long id).

In my app I will have many dictionaries like classes, that will have only id and name parameters, so I would like to write once functionality and use it through inheritance in rest classes:

Here is server implementation:

Domain model classes

@MappedSuperclass
public  class GenericModel<T extends GenericModel<T>> implements Identifiable, Versionable {
    @Transient
    protected Class<T> entityClass;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Version
    private Integer version;

    // setter & getter
    @Override
    public Long getId() {return id;}
    public void setId(Long id) {this.id = id;}
    @Override
    public Integer getVersion() {return version;}
    public void setVersion(Integer version) {this.version = version;}

    // constructor
    public GenericModel() {
        Class<?> obtainedClass = getClass();
        Type genericSuperclass = null;
        for (;;) {
            genericSuperclass = obtainedClass.getGenericSuperclass();
            if (genericSuperclass instanceof ParameterizedType) {
                break;
            }
            obtainedClass = obtainedClass.getSuperclass();
        }
        ParameterizedType genericSuperclass_ = (ParameterizedType) genericSuperclass;
        try {
            entityClass = ((Class) ((Class) genericSuperclass_
                    .getActualTypeArguments()[0]));
        } catch (ClassCastException e) {
            entityClass = guessEntityClassFromTypeParametersClassTypedArgument();
        }
    }


    public GenericModel(Long id) {
        this();
        this.id = id;
    }
}

   @MappedSuperclass
    public abstract class GenericDictionaryModel<T extends GenericModel<T>> extends GenericModel<T> {

        private String name;
        @Transient
        private String optionDisplayName;
    //  private boolean active = true;

        public String getName() {return name;}
        public void setName(String name) {this.name = name;}
    //  public boolean getActive() {return active;}
    //  public void setActive(boolean stat) {this.active = stat;}
        public String getOptionDisplayName() {return optionDisplayName;}
        public void setOptionDisplayName(String optionDisplayName) {this.optionDisplayName = optionDisplayName;}

        public GenericDictionaryModel() {
            super();
        }
        public GenericDictionaryModel(Long id, String name) {
            super(id);
            this.name = name;
        }

    }

@Entity
public class PageI extends GenericDictionaryModel<PageI> {

    @ManyToMany(cascade = CascadeType.ALL)
    private List<Content> contents;


    /* Getters and Setters */
    public List<Content> getContents() {
        return contents;
    }

    public void setContents(List<Content> contents) {
        this.contents = contents;
    }



}

DAO classes

public class GenericDao<T extends GenericModel<T>> {
    private Logger logger = LoggerFactory.getLogger(this.getClass().getCanonicalName());
    @Transient protected Class<T> entityClass;


    public GenericDao() {
        super();
    }
    public GenericDao(Class<? extends GenericModel<T>> clazz) {
        this.entityClass = (Class<T>) clazz;
    }

    public T getBy(Long id) {
        return JPA.em().find(entityClass, id);
    }
    public List<GenericModel<T>> get() {
        logger.error("trying to get data from db");
        return getList();
    }
//  public List<T> get() {
//  }
    public List<GenericModel<T>> getList() {
        List<T> resultList = JPA.em().createQuery("FROM " + entityClass.getSimpleName()).getResultList();

        logger.error(resultList.toString());
        return JPA.em().createQuery("FROM " + entityClass.getSimpleName()).getResultList();
    }
}

public class GenericDictionaryDao<T extends GenericDictionaryModel<T>> extends GenericDao<T>{
    private Logger logger = LoggerFactory.getLogger(this.getClass().getCanonicalName());

//  public T getBy(Long id) {
//      return super.getBy(id);
//    }
    public List<GenericModel<T>> getByName() {
        return super.get();
    }



//  public List<T> getListOrderedByName() {
//  public List<GenericDictionaryModel> getListOrderedByName() {
    public List<GenericDictionaryModel> getListOrderedByName2() {
        return null;
    }
    public List<GenericDictionaryModel<T>> getListOrderedByName() {
        try {
            return JPA.em()
                      .createQuery("FROM " + entityClass.getSimpleName() + " ORDER BY name")
                      .getResultList();
        } catch (ClassCastException e) {
            return new LinkedList<GenericDictionaryModel<T>>();
        }
//      return null;
    }
}

Here is shared implementation:

Proxies:

@ProxyFor(value = GenericModel.class, locator = GenericLocator.class)
public interface GenericProxy extends EntityProxy {
    public Long getId();
    public void setId(Long id);
    public Integer getVersion();
    public void setVersion(Integer version);
}

@ProxyFor(value = GenericDictionaryModel.class, locator = GenericLocator.class)
public interface GenericDictionaryProxy extends GenericProxy {
    public String getName();
    public void setName(String name);
}

@ProxyFor(value = PageI.class, locator = GenericLocator.class)
public interface PageIProxy extends GenericDictionaryProxy {
    public List<ContentIProxy> getContents();
    public void setContents(List<ContentIProxy> contents);
    public static final String Contents = "contents";
}

And Contexts/ Services:

@Service(value = GenericDao.class, locator = MyServiceLocator.class)
@ExtraTypes( {
    GenericProxy.class 
    } )
public interface GenericContext extends RequestContext {
    Request<GenericProxy> getBy(Long id);
    Request<List<GenericProxy>> get();
    Request<Void> save(GenericProxy entity);
}

@Service(value = GenericDictionaryDao.class, locator = MyServiceLocator.class)
@ExtraTypes( {
    GenericDictionaryProxy.class,
    PageIProxy.class,
    ContentIProxy.class
    } )
public interface GenericDictionaryContext extends GenericContext {
    public Request<List<GenericDictionaryProxy>> getListOrderedByName();
}

public interface Services extends RequestFactory {
    GenericContext getGenericContext();
    GenericDictionaryContext getGenericDictionaryContext();

}

Here is client execution implementation:

    List<GenericDictionaryProxy> proxies = new LinkedList<GenericDictionaryProxy>();
    GenericDictionaryContext context = createRequestFactory().getGenericDictionaryContext();

    context.get().to(new Receiver<List<GenericDictionaryProxy>>() {
        @Override
        public void onSuccess(List<GenericDictionaryProxy> response) {
            for(GenericDictionaryProxy p: response) {
                cont.add(new Label(p.getId() + " " + p.getName() + ", " + p.getVersion() ));
            }
        }
    }).fire();

Which should return me a List of object with parameters: id, version, name.

Unfortunatelly it dosent work.

My IDE shows me errors:

Could not find domain method similar to java.util.List<pl.derp.server.model.GenericDictionaryModel<T>> getListOrderedByName()   GenericDictionaryContext.java   /Index/src/main/java/pl/derp/shared/rf  line 26 Annotation Problem (Java 6 processor)
The method to(Receiver<? super List<GenericProxy>>) in the type Request<List<GenericProxy>> is not applicable for the arguments (new Receiver<List<GenericDictionaryProxy>>(){})    GoodbyeViewImpl.java    /Index/src/main/java/pl/derp/client/view    line 91 Java Problem

Here are compilation errors:

[INFO]    Tracing compile failure path for type 'pl.derp.client.view.GoodbyeViewImpl'
[INFO]       [ERROR] Errors in 'file:/home/korbeldaniel/git/derp3/tutorial/src/main/java/pl/derp/client/view/GoodbyeViewImpl.java'
[INFO]          [ERROR] Line 91: The method to(Receiver<? super List<GenericProxy>>) in the type Request<List<GenericProxy>> is not applicable for the arguments (new Receiver<List<GenericDictionaryProxy>>(){})
[INFO]    [ERROR] Hint: Check the inheritance chain from your module; it may not be inheriting a required module or a module may not be adding its source path entries properly

Please tell me what I am doing wrong.


Solution

  • Yes this will work for you but you need to have the @ExtraTypes annotation on your RequestContext for all the different types you will need.

    I posted a full example a while back.

    GWT polymorphic lists with @ExtraTypes

    Edit

    To get this to work you should make your generic requestcontext use generics. This is what I have done in the past and it works correctly for me. You won't need the extra types on the generic to make this work because you are going to be telling it the type.

    @Service(value = GenericDao.class, locator = MyServiceLocator.class)
    public interface GenericContext<T extends GenericProxy> extends RequestContext {
        Request<T> getBy(Long id);
        Request<List<T>> get();
        Request<Void> save(T entity);
    }
    
    
    @Service(value = GenericDictionaryDao.class, locator = MyServiceLocator.class)
    @ExtraTypes( {
        GenericDictionaryProxy.class,
        PageIProxy.class,
        ContentIProxy.class
        } )
    public interface GenericDictionaryContext extends GenericContext<GenericDictionaryProxy> {
        public Request<List<GenericDictionaryProxy>> getListOrderedByName();
    
        // These used to be required due to a bug a while ago. Test without it
        // but if you get a method about an unknown method then this is the issue.
        Request<GenericDictionaryProxy> getBy(Long id);
        Request<List<GenericDictionaryProxy>> get();
        Request<Void> save(T entity);
    }
    

    There was a problem I found a while ago, not sure if it has been fixed but I also had to add the methods to the extending class. I didn't mind because I was still allowed to use my GenericContext when needed and everything worked. This allowed me to create a nice entity caching mechanism with guava LoadingCache.

    Quick example.

    public class EntityCache<T extends GenericProxy, R extends GenericContext<T>>  {
    
        private R requestContext;
    
        public EntityCache(R requestContext) {
            this.requestContext = requestContext;
        }
    
        public T get(Long key) {
           // get from loading cache but this is a simple example.
           requestContext.get(key);
        }
    
    }