Guava Group By Key - guava

Still learning all the features of the Guava API. Wanted to shoot this out there to see if there were any suggestions on how to go about doing this. The current picture looks like this: List < ObjectA >, List< ObjectB >, List< ObjectC >
ObjectA {
Integer attribute1;
String attribute2;
….
}
ObjectB {
Integer attribute1;
String attribute2;
….
}
ObjectC {
Integer attribute1;
String attribute2;
….
}
I would like to take the 3 lists and group the objects together by attribute1 and attribute2 thus creating something like this:
123|”Whatever” : { Object A, ObjectB, ObjectC}
456|”Something” : { Object A, ObjectB, ObjectC}
An obvious *Multimap is the return but the process of building this could have multiple solutions. Thoughts on how to approach this?

I would use Multimaps.index() with a custom pair class to represent the combination of attribute1 and attribute2.
/**
* A "pair" class that contains an {#code attribute1} and an {#code attribute2}. Mainly used as a {#link Map} key.
*/
#Immutable
public class Attribute1AndAttribute2 {
#Nullable
private final Integer attribute1;
#Nullable
private final String attribute2;
public Attribute1AndAttribute2(#Nullable Integer attribute1,
#Nullable String attribute2) {
this.attribute1 = attribute1;
this.attribute2 = attribute2;
}
#Nullable
public Integer getAttribute1() {
return attribute1;
}
#Nullable
public String getAttribute2() {
return attribute2;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Attribute1AndAttribute2) {
Attribute1AndAttribute2 that = (Attribute1AndAttribute2) obj;
return Objects.equal(this.attribute1, that.attribute1)
&& Objects.equal(this.attribute2, that.attribute2);
}
return false;
}
#Override
public int hashCode() {
return Objects.hashCode(attribute1, attribute2);
}
}
/**
* Static utility methods pertaining to {#link ObjectA}, {#link ObjectB}, and {#link ObjectC}'s {#code attribute1} and
* {#code attribute2}.
*/
public final class Attribute1AndAttribute2Utils {
private Attribute1AndAttribute2Utils() { /* prevents instantiation */ }
public static Multimap<Attribute1AndAttribute2, Object> groupedByAttribute1AndAttribute2(List<ObjectA> as, List<ObjectB> bs, List<ObjectC> cs) {
Iterable<Object> abcs = Iterables.concat(as, bs, cs);
return Multimaps.index(abcs, Attribute1AndAttribute2ForObjectFunction.INSTANCE);
}
// enum singleton pattern
private enum Attribute1AndAttribute2ForObjectFunction implements Function<Object, Attribute1AndAttribute2> {
INSTANCE;
#Override
public Attribute1AndAttribute2 apply(#Nullable Object object) {
if (object instanceof ObjectA) {
ObjectA objectA = (ObjectA) object;
return new Attribute1AndAttribute2(objectA.attribute1, objectA.attribute2);
} else if (object instanceof ObjectB) {
ObjectB objectB = (ObjectB) object;
return new Attribute1AndAttribute2(objectB.attribute1, objectB.attribute2);
} else if (object instanceof ObjectC) {
ObjectC objectC = (ObjectC) object;
return new Attribute1AndAttribute2(objectC.attribute1, objectC.attribute2);
} else {
throw new RuntimeException("Object must be ObjectA, ObjectB, or ObjectC, but was " + object);
}
}
}
}
Note that the code would a be a lot cleaner / better if you had an interface such as:
public interface HasAttribute1AndAttribute2 {
Integer getAttribute1();
String getAttribute2();
}
Also, I assumed that attribute1 and attribute2 may be null. If they are never null, consider changing the pair class to:
/**
* A "pair" class that contains an {#code attribute1} and an {#code attribute2}. Mainly used as a {#link Map} key.
*/
#Immutable
public class Attribute1AndAttribute2 {
#Nonnull
private final Integer attribute1;
#Nonnull
private final String attribute2;
public Attribute1AndAttribute2(#Nonnull Integer attribute1, #Nonnull String attribute2) {
this.attribute1 = checkNotNull(attribute1);
this.attribute2 = checkNotNull(attribute2);
}
#Nonnull
public Integer getAttribute1() {
return attribute1;
}
#Nonnull
public String getAttribute2() {
return attribute2;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Attribute1AndAttribute2) {
Attribute1AndAttribute2 that = (Attribute1AndAttribute2) obj;
return this.attribute1.equals(that.attribute1)
&& this.attribute2.equals(that.attribute2);
}
return false;
}
#Override
public int hashCode() {
return Objects.hashCode(attribute1, attribute2);
}
}

Related

How to work with PGpoint for Geolocation using PostgreSQL?

I found a lot of answers that suggest to use spatial data with Hibernate spatial data geolocation but I want to know if that is the best because I found that PostgreSQL works with PGpoint for GeoLocation. I implemented but it doesn't work because doesn't save.
ERROR: column "location" is of type point but expression is of type character varying
I have the same question but nobody answered him. So let me add other question below if nobody knows about this question.
As suggestion I'd want to know what is the best way to use Geo data on Spring Boot Context
Thanks! have a good day.
There is no way to save/update/get/ PGpoint object directly,
Then you have to create your own user type for supporting PGpoint in order to convert it, before this is saved, UserType is a class of Hibernate which allows to create custom type in order to convert it before to save on database.
Here is code that you need to implement:
First: Need to create a class that implements of UserType:
public class PGPointType implements UserType {
#Override
public int[] sqlTypes() {
return new int[]
{
Types.VARCHAR
};
}
#SuppressWarnings("rawtypes")
#Override
public Class<PGpoint> returnedClass() {
return PGpoint.class;
}
#Override
public boolean equals(Object obj, Object obj1) {
return ObjectUtils.equals(obj, obj1);
}
#Override
public int hashCode(Object obj) {
return obj.hashCode();
}
#Override
public Object nullSafeGet(ResultSet resultSet, String[] names, SharedSessionContractImplementor sharedSessionContractImplementor, Object o) throws SQLException {
if (names.length == 1) {
if (resultSet.wasNull() || resultSet.getObject(names[0]) == null) {
return null;
} else {
return new PGpoint(resultSet.getObject(names[0]).toString());
}
}
return null;
}
#Override
public void nullSafeSet(PreparedStatement statement, Object value, int index, SharedSessionContractImplementor sharedSessionContractImplementor) throws SQLException {
if (value == null) {
statement.setNull(index, Types.OTHER);
} else {
statement.setObject(index, value, Types.OTHER);
}
}
#Override
public Object deepCopy(Object obj) {
return obj;
}
#Override
public boolean isMutable() {
return Boolean.FALSE;
}
#Override
public Serializable disassemble(Object obj) {
return (Serializable) obj;
}
#Override
public Object assemble(Serializable serializable, Object obj) {
return serializable;
}
#Override
public Object replace(Object obj, Object obj1, Object obj2) {
return obj;
}
}
Second: Need to add on entity header #TypeDef annotation, add a name and the PGPointType that you created it and on some field header of type PGpoint, add #Type annotation with the name that you created it:
#TypeDef(name = "type", typeClass = PGPointType.class)
#Entity
public class Entity {
#Type(type = "type")
private PGpoint pgPoint;
// Getters and setters
}
Kind regards.

JPA Eclipselink JOIN FETCH LAZY relation returning null

I am always getting NULL from a JOIN FETCH clause in my JPA Query, even though I have everything configured as expected:
#XmlRootElement
#XmlAccessorType(XmlAccessType.PROPERTY)
#Entity
#Table(name = "TB_BANNER_IMAGE")
public class BannerImage extends BaseEntity<Integer> {
protected FileReference fileReference;
private String type;
private String labelTitle;
protected BannerImage() {}
#Id
#TableGenerator(name="genBannerImage", table="TB_ID_GENERATOR",
pkColumnName="ID_NAME", valueColumnName="ID_VAL",
pkColumnValue="TB_BANNER_IMAGE", allocationSize=1)
#GeneratedValue(strategy=GenerationType.TABLE, generator="genBannerImage")
#Column(name = "ID_BANNER_IMAGE", unique = true, nullable = false)
public Integer getId() {
return super.getId();
}
#Override
public void setId(Integer id) {
super.setId(id);
}
#Column(name="TYPE")
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
#OneToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#JoinColumn(name="ID_FILE_REFERENCE", nullable=false)
public FileReference getFileReference() {
return fileReference;
}
public void setFileReference(FileReference fileReference) {
this.fileReference = fileReference;
}
#Column(name="LABEL_TITLE")
public String getLabelTitle() {
return labelTitle;
}
public void setLabelTitle(String labelTitle) {
this.labelTitle = labelTitle;
}
}
for File Reference Class:
#Entity
#Table(name = "TB_FILE_REFERENCE")
public class FileReference extends BaseNamedEntity<String> {
private String type;
public FileReference() {}
#Id
#TableGenerator(name="genFileReference", table="TB_ID_GENERATOR",
pkColumnName="ID_NAME", valueColumnName="ID_VAL",
pkColumnValue="TB_FILE_REFERENCE", allocationSize=1)
#GeneratedValue(strategy=GenerationType.TABLE, generator="genFileReference")
#Column(name = "ID_FILE_REFERENCE", unique = true, nullable = false)
public String getId() {
return super.getId();
}
#Override
public void setId(String id) {
super.setId(id);
}
#Column(name = "TYPE")
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
Service class:
#Path("/banner")
public class BannerImageService extends BaseServiceFacade<BannerImage, Integer> {
#SuppressWarnings("unchecked")
#Override
public Crud<BannerImage, Integer> lookupService() throws ServiceLocatorException {
return ServiceLocator.getInstance()
.getLocalHome(ServicesConstants.BANNER_IMAGE_SERVICE);
}
#Override
protected String getDefaultGetQuery() {
return BannerImageDAO.GET_BY_ID_FETCH_FILE_REF;
}
#Override
protected String getDefaultQuery() {
return BannerImageDAO.GET_ALL_FETCH_FILE_REF;
}
}
get REST method of BaseServiceFacade:
#Override
#GET
#Consumes(MediaType.APPLICATION_JSON)
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Path("/{id}")
public T get(#PathParam("id") ID id) {
try {
if (!validateID(id)) {
logMessage("Invalid Entity ID: " + id);
return null;
}
String defaultGetQuery = getDefaultGetQuery();
if (defaultGetQuery != null) {
Map<String, Object> mapParams = new HashMap<String, Object>();
mapParams.put("id", id);
List<T> entityList = getService().search(defaultGetQuery, mapParams);
if (entityList != null && entityList.size() == 1) {
T ent = entityList.get(0);
return ent;
} else {
logMessage("Invalid search by Entity ID: " + id);
}
} else {
return getService().findById(clazz, id);
}
} catch (ServiceException e) {
serviceException(e);
} catch (Exception ex) {
logException(ex);
}
return null;
}
And finally the Service Bean EJB which reads from entityManager:
public class BaseServiceBean<T extends IEntity<ID>, ID extends Serializable> implements Crud<T,ID> {
// ... generic methods to be reused by subclasses
#Override
public List<T> search(String queryOrNamedQuery) throws ServiceException {
return search(queryOrNamedQuery, null, 0, 0);
}
#SuppressWarnings("unchecked")
public List<T> search(String namedQueryOrHql, Map<String, Object> parameters, int start, int chunkSize) {
try {
Query query = createQuery(namedQueryOrHql, getQueryType(namedQueryOrHql));
if (start > 0) {
query.setFirstResult(start);
}
if (chunkSize > 0) {
query.setMaxResults(chunkSize);
}
addParameters(query, parameters);
List<T> result = query.getResultList();
afterSearch(result);
return result;
} catch (NoResultException nre) {
nre.printStackTrace();
} catch (ClassCastException cce) {
cce.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
protected void afterSearch(List<T> result) {
}
// etc...
implementation specific class for BannerImageService:
#Stateless(mappedName="ejb/BannerImageService")
public class BannerImageServiceBean extends BaseServiceBean<BannerImage, Integer> implements BannerImageServiceBeanRemote, BannerImageServiceBeanLocal {
#Override
protected void afterSearch(List<BannerImage> result) {
if (result != null && result.size() == 1) {
BannerImage bannerImage = result.get(0);
bannerImage.getFileReference();
}
super.afterSearch(result);
}
// additional code ...
When I try to fetch my BannerImage class together with it's corresponding FileReference member I always get NULL even though in my DB there is an existing foreign key present:
JPQL:
"SELECT a FROM BannerImage a join fetch a.fileReference WHERE a.id = :id";
Generated SQL:
SELECT t1.ID_BANNER_IMAGE, t1.LABEL_TEXT, t1.LABEL_TITLE, t1.TYPE,
t1.ID_FILE_REFERENCE, t0.ID_FILE_REFERENCE, t0.NAME,
t0.TYPE FROM TB_FILE_REFERENCE t0, TB_BANNER_IMAGE
t1 WHERE (t0.ID_FILE_REFERENCE = t1.ID_FILE_REFERENCE) AND t1.ID_BANNER_IMAGE = 1
in my DB the record shows a correct reference:
BANNER_IMAGE:
1;"";"main";"2bdbb063d0d0ee2939c89763945d9d9e";"banner1.png";"image/png"
If I execute :
select * from TB_FILE_REFERENCE where ID_FILE_REFERENCE = '2bdbb063d0d0ee2939c89763945d9d9e'
I can find the record in the DB, although my EclipseLink JPA Implementation always returns null:
EclipseLink Version 2.5.2-M1
This is how the Entity gets passed from Service Layer to the
Can someone help pointing why the JOIN FETCH is not properly working?
I faced a similar issue and looking closely I see that this issue was happening only to entities recently created/saved. Then I figured that it has something to do with eclipselink cache. I solved this problem by adding this line before making a join fetch JPQL query,
em.getEntityManagerFactory().getCache().evictAll();
em.createQuery("SELECT a FROM BannerImage a join fetch a.fileReference WHERE a.id = :id").getResultList();
HTH!

JPA, How to find an object that has composite id?

Based on second approach answered here I designed my JPA class.
#Entity(name = "SearchKeywordJPA")
#IdClass(SearchKeywordJPA.SearchKeyId.class)
public class SearchKeywordJPA implements Comparable<SearchKeywordJPA> {
#Id
private String keyword;
#Id
private long date;
private String userUUID;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SearchKeywordJPA that = (SearchKeywordJPA) o;
if (date != that.date) return false;
if (!keyword.equals(that.keyword)) return false;
if (!userUUID.equals(that.userUUID)) return false;
return true;
}
#Override
public int hashCode() {
int result = keyword.hashCode();
result = 31 * result + (int) (date ^ (date >>> 32));
result = 31 * result + userUUID.hashCode();
return result;
}
#Override
public String toString() {
return "SearchKeywordJPA{" +
"keyword='" + keyword + '\'' +
", date=" + date +
", userUUID='" + userUUID + '\'' +
'}';
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
public long getDate() {
return date;
}
public void setDate(long date) {
this.date = date;
}
public String getUserUUID() {
return userUUID;
}
public void setUserUUID(String userUUID) {
this.userUUID = userUUID;
}
#Override
public int compareTo(SearchKeywordJPA searchRecord) {
long comparedDate = searchRecord.date;
if (this.date > comparedDate) {
return 1;
} else if (this.date == comparedDate) {
return 0;
} else {
return -1;
}
}
/**********************
* Key class
**********************/
public class SearchKeyId {
private int id;
private int version;
}
}
In my servlet I want to check datastore and store my object if is not exist.
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
for(SearchKeywordJPA item: applicationList) {
if(!isRecorded(item))
storeRecord(item);
}
}
private boolean isRecorded(SearchKeywordJPA record) {
EntityManager em = EMF.get().createEntityManager();
SearchKeywordJPA item = em.find(SearchKeywordJPA.class, record);
return item != null;
}
private void storeRecord(SearchKeywordJPA record) {
EntityManager em = EMF.get().createEntityManager();
em.persist(record);
}
However when I run, application crashes and log says
javax.persistence.PersistenceException: org.datanucleus.store.appengine.FatalNucleusUserException: Received a request to find an object of type com.twitterjaya.model.SearchKeywordJPA identified by SearchKeywordJPA{keyword='airasia', date=1335680686149, userUUID='FFFF0000'}. This is not a valid representation of a primary key for an instance of com.twitterjaya.model.SearchKeywordJPA.
What is the reason? any suggestion would be appreciated. Thanks
You pass an instance of the IdClass into em.find ... i.e SearchKeyId. Obviously if you really have an IdClass that has no equals/hashCode/toString/constructor then you will likely get many problems. Those problems will only be increased by using an ancient plugin for GAE/Datastore.
If your Key is
#Entity(name = "SearchKeywordJPA")
#IdClass(SearchKeywordJPA.SearchKeyId.class)
public class SearchKeywordJPA implements Comparable<SearchKeywordJPA> {
you are doing it wrong.
IdClass does not need any annotation of #IdClass just the #Id
annotation.
Key can not be an entity.
Need to implements Serializable , comparable is not needed
Need to override equals and hascode and have no arg constructor
Class key need to be as follows.
public class SearchKeyId implements Serializable {
private String keyword;
private long date;
And your entity I assume something like this.
#Entity(name = "SearchKeywordJPA")
#IdClass(SearchKeyId.class)
public class SearchKeywordJPA {
#Id
private String keyword;
#Id
private long date;
private String userUUID;
Just consider that find method will use the SearchKey.class to find
the entities.
Fields that are in the IdClass need to have #Id annotation in the entity.
Key can not be an entity on its own.
Comparable is not really needed as all the comparison are placed in the IdClass

JSF2.0, JPA: How to set list element (mapped in #OneToMany)

I'm looking for good practices and clean solution to my problem.
There are 2 entity classes:
#Entity
public class Site implements Serializable {
...
#OneToMany(mappedBy = "site")
private List<SiteIp> siteIpList;
...
public List<SiteIp> getSiteIpList() {
return siteIpList;
}
public void setSiteIpList(List<SiteIp> siteIpList) {
this.siteIpList = siteIpList;
}
}
#Entity
#IdClass(SiteIpPK.class)
public class SiteIp implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY)
private Site site;
#Id
private int idx;
private String ip;
/* other stuff such as constructors, getters and setters */
}
SiteIpPK has 2 columns, it should be clean :)
Obviously, this is commonly used model in world.
Next, there is a view layer. JSF page has 3 fields showing SiteIp.ip for given site and idx. As far, I've wrote helper getter method on Site entity:
public String getIpForIdx(Integer idx) {
// there's no simple siteIpList.get(idx), because of additional logic in getter
// so let's iterate through entire list
for (SiteIp siteIp : this.siteIpList) {
if (/* other logic returns true value && */ siteIp.getIdx() == idx) {
return siteIp.getIp();
}
}
return null;
}
JSF expression language is constructed as follows:
<h:inputText id="sync1_ip" value="#{siteController.editContext.site.getIpForIdx(1)}" />
Now, when page is accessed, proper IP values is propagated to input text field, as far it's all good. But, when IP changed and form is submitted, EL throws exception:
javax.el.PropertyNotWritableException: /edit/siteEdit.xhtml #47,123 value="#{siteController.editContext.site.getIpForIdx(1)}": Illegal Syntax for Set Operation
I understand and acknowledge that, so there is my question:
What are best practices to handle this issue? Somewhat awful solution is to write as many helper methods as unique indexes exists:
public String getIp1() {
return this.IpForIdx(1);
}
public String getIp2() {
return this.IpForIdx(2);
}
/* ... */
public void setIp1(String newIp) {
this.siteIpList.add(1, newIp);
}
public void setIp2(String newIp) {
this.siteIpList.add(2, newIp);
}
/* ... and so on... */
with JSF EL:
<h:inputText id="sync1_ip" value="#{siteController.editContext.site.ip1}" />
Are there other, more flexible and beautiful solutions?
You should better google for 'composite primary key with JPA'. But as you're new here, below goes a simple explanation and a working solution.
Small explanation
Composite primary keys are themselves classes with #Embeddable annotation. And they become a primary key in your #Entity class as soon as you mark your primary composite key field with #EmbeddedId. The only thing left is to implement equals and hashcode appropriately in both classes.
Solution
The solution consists of an #Entity class
#Entity
#Table(name="...")
#AssociationOverride(name = "pk.site", joinColumns = #JoinColumn(name = "..."))
public class SiteIp implements Serializable {
private SiteIpPk pk = new SiteIpPK();
private String ip;
public SiteIp() { }
#EmbeddedId
public SiteIpPK getPk() {
return pk;
}
public void setPk(SiteIpPk pk) {
this.pk = pk;
}
#Transient
public Site getSite() {
return pk.getSite();
}
public void setSite(Site site) {
pk.setSite(site);
}
#Transient
public Integer getIdx() {
return pk.getIdx();
}
public void setIdx(Integer idx) {
pk.setIdx(idx);
}
#Column(name="...")
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
SiteIp that = (SiteIp) o;
if (pk != null ? !pk.equals(that.pk) : that.pk != null)
return false;
return true;
}
public int hashCode() {
return (pk != null ? pk.hashCode() : 0);
}
}
and #Embeddable class
#Embeddable
public class SiteIpPk implements Serializable {
private Site site;
private Integer idx;
#ManyToOne
public Site getSite() {
return site();
}
public void setSite(Site site) {
this.site = site;
}
#Column(name="...", nullable = false)
public Integer getIdx() {
return idx;
}
public void setIdx(Integer idx) {
this.idx = idx;
}
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
SiteIpPk that = (SiteIpPk) o;
if (site != null ? !site.equals(that.site) : that.site != null)
return false;
if (idx != null ? !idx.equals(that.idx) : that.idx != null)
return false;
return true;
}
public int hashCode() {
int result;
result = (site != null ? site.hashCode() : 0);
result = 17 * result + (idx != null ? idx.hashCode() : 0);
return result;
}
}
In the end, learn more yourself and check if the problem has already been investigated!

In Spring-mvc the attribute names in view have to always match the property names in model?

In the http request body, the way password string is passed is "pass=1111", however in the bean the way password is defined is ''private String password". Is there a way I can use annotation to handle the difference or I have to always match names?
The Http request is like this
curl -H "Accept:text/html" -H "Content-Type application/x-www-form-urlencoded" -d 'email=test%40gmail.com&pass=1111&passconfirm=1111&name=x+y' "http://localhost:8080/project/register"
Handler method is
#RequestMapping(method = RequestMethod.POST, headers = "content-type=application/x-www-form-urlencoded")
public String register(#ModelAttribute UserAccountBean account) ...
UserAccountBean is
public class UserAccountBean2 {
#NotNull
#Size(min = 1, max = 25)
private String name;
#NotNull
#Size(min = 4, max = 8)
private String password;
#NotNull
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
public String toString() {
return new ToStringCreator(this).append("name", name).append("password", password).toString();
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
Use #RequestParam annotation in #InitBinder annotated method, and set the desired value manually.
UserController
#InitBinder(value="user")
public void bind(WebDataBinder dataBinder, WebRequest webRequest, #RequestParam(value="pass", required=false) String password) {
User user = (User) dataBinder.getTarget();
user.setPassword(password);
}
Is there a way I can use annotation to
handle the difference or I have to
always match names?
AFAIK there is no ready-made annotation in Spring MVC that can resolve your problem; you need custom setup to handle the situation.
WebModelAttribute
#Target({ElementType.METHOD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface WebModelAttribute {
String modelAttributeName();
WebParameterMapping[] parameterMappings();
}
WebParameterMapping
#Target({ElementType.METHOD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface WebParameterMapping {
String webProperty();
String beanProperty();
}
UserController
#Controller
public class UserController extends AbstractController {
#Override
#InitBinder(value="user")
#WebModelAttribute(modelAttributeName="user", parameterMappings={#WebParameterMapping(webProperty="pass", beanProperty="password")})
protected void bindWebParameters(WebDataBinder dataBinder, WebRequest webRequest, WebParameterResolver mappingResolver) {
super.bindWebParameters(dataBinder, webRequest, mappingResolver);
}
AbstractController
public class AbstractController {
protected void bindWebParameters(WebDataBinder dataBinder, WebRequest webRequest, WebParameterResolver mappingResolver) {
if(mappingResolver != null && dataBinder.getTarget() != null && dataBinder.getObjectName().equals(mappingResolver.getModelAttributeName())) {
String[] allowedFields = mappingResolver.getAllowedFields(dataBinder.getAllowedFields());
String[] disallowedFields = mappingResolver.getDisallowedFields(dataBinder.getDisallowedFields());
dataBinder.setAllowedFields(allowedFields);
dataBinder.setDisallowedFields(disallowedFields);
dataBinder.bind(mappingResolver.getPropertyValues(dataBinder, webRequest));
}
}
}
WebParameterResolver
public class WebParameterResolver {
private String modelAttributeName;
private WebParameterMapping[] parameterMappings;
public WebParameterResolver(String modelAttributeName,
WebParameterMapping[] parameterMappings) {
this.modelAttributeName = modelAttributeName;
this.parameterMappings = parameterMappings;
}
public String getModelAttributeName() {
return modelAttributeName;
}
public String[] getDisallowedFields(String[] existingDisallowedFields) {
List<String> disallowedFields = new ArrayList<String>();
for (WebParameterMapping parameterMapping : parameterMappings) {
disallowedFields.add(parameterMapping.webProperty());
}
if (existingDisallowedFields != null) {
for (String disallowedField : existingDisallowedFields) {
disallowedFields.add(disallowedField);
}
}
return disallowedFields.toArray(new String[disallowedFields.size()]);
}
public String[] getAllowedFields(String[] existingAllowedFields) {
List<String> allowedFields = new ArrayList<String>();
for (WebParameterMapping parameterMapping : parameterMappings) {
allowedFields.add(parameterMapping.beanProperty());
}
if (existingAllowedFields != null) {
for (String allowedField : existingAllowedFields) {
allowedFields.add(allowedField);
}
}
return allowedFields.toArray(new String[allowedFields.size()]);
}
public MutablePropertyValues getPropertyValues(WebDataBinder dataBinder,
WebRequest webRequest) {
MutablePropertyValues propertyValues = new MutablePropertyValues();
for (WebParameterMapping parameterMapping : parameterMappings) {
String[] values = webRequest.getParameterValues(parameterMapping.webProperty());
if (values == null || values.length == 0) {
// do nothing
} else if (values.length == 1) {
propertyValues.add(parameterMapping.beanProperty(), values[0]);
} else {
propertyValues.add(parameterMapping.beanProperty(), values);
}
}
dataBinder.bind(propertyValues);
return propertyValues;
}
}
CustomArgumentResolver
public class CustomArgumentResolver implements WebArgumentResolver {
#Override
public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception {
if(methodParameter.getParameterType().equals(WebParameterResolver.class)) {
WebModelAttribute webModelAttribute = methodParameter.getMethod().getAnnotation(WebModelAttribute.class);
if(webModelAttribute == null) {
throw new RuntimeException("method must have WebModelAttribute");
}
return new WebParameterResolver(webModelAttribute.modelAttributeName(), webModelAttribute.parameterMappings());
}
return UNRESOLVED;
}
}
beans.xml
<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="customArgumentResolvers" ref="timetracking.annotations.CustomArgumentResolver"/>
</bean>
<bean name="timetracking.annotations.CustomArgumentResolver"
class="timetracking.annotations.CustomArgumentResolver" />
You can also have a public static void bindWebParameters(...) method in some helper class; so you don't have to extend the AbstractController every time.
You can achieve it with this:
#RequestMapping(method = RequestMethod.POST, headers = "content-type=application/x-www-form-urlencoded")
public String register(#ModelAttribute("userAccountBean") UserAccountBean account) ...
#ModelAttribute("userAccountBean")
public UserAccountBean getUserAccountBean(HttpServletRequest req) {
UserAccountBean uab = new UserAccountBean();
uab.setPassword(req.getParameter("pass"));
return uab;
}
There is no annotation based solution in 3.0.
Just provide additional getPass() setPass(String pass) method and you should be set.