selectOneMenu with complex objects, is a converter necessary? [duplicate] - jsf-2

This question already has answers here:
Conversion Error setting value for 'null Converter' - Why do I need a Converter in JSF?
(2 answers)
Closed 7 years ago.
Is a Converter necessary for a <h:selectOneMenu> or <p:selectOneMenu> with arbitrary user-created classes as its values? I mean, is the following code supposed to work without a converter?
<p:selectOneMenu value="#{bean.language}">
<f:selectItems value="#{bean.allLanguages}" />
</p:selectOneMenu>
and
#Named(value = "bean")
#ConversationScoped
public class Bean {
private Language language; // appropriate getter and setter are present
public List<SelectItem> getAllLanguages() {
// populates a list of select items with Strings as item labels and Languages as item values
}
}
I have a similar code with an enum as the type (Language) and it works perfectly. But when I replace the type with a normal java class, I get a conversion error.

You need a converter here, as JSF will assume strings by default, that is the way you coded it. JSF has no idea how to convert your pseudo entities to strings and vice versa.
Some notes:
1 . Your getAsString method defines your identifier for your entities/POJOs, not what the JSF (or whatever) select gets as itemLabel.
2 . Your converter can dig into the DB for real entities using this infamous article:
http://balusc.blogspot.de/2011/09/communication-in-jsf-20.html#ConvertingAndValidatingGETRequestParameters
You can also use CDI annotations with that "pattern".
3 . Your value = "bean"is redundant and the CDI scope of choice is usually #ViewScoped. However, you have to keep in mind that CDI #Named + JSF #ViewScoped isn't working together without using Seam 3 or Apache MyFaces CODI.

You do not need a converter, if you use this little class that I wrote :-) It can back selectOne and selecteMany components. It requires that your class's toString() provides a one-to-one unique representation of your object. If you like, you could substitute a method name other than toString(), like toIDString()
To use ListBacker in your ManagedBean, use ListBacker<Type> wherever you would have used List<Type>
#ManagedBean
#RequestScoped
public class BackingBean {
private ListBacker<User> users; // +getter +setter
#PostConstruct
public void init() {
// fill it up from your DAO
users = new ListBacker<User>(userDAO.find());
}
// Here's the payoff! When you want to use the selected object,
// it is just available to you, with no extra database hits:
User thisOneIsSelected = users.getSelectedItemAsObject();
// or for multi-select components:
List<User> theseAreSelected = users.getSelectedItemsAsObjects();
}
In your xhtml file:
<p:selectOneMenu value="#{backingBean.users.selectedItem}">
<f:selectItems value="#{backingBean.users.contents}" var="item" itemValue="#{item.value}" itemLabel="#{item.label}" />
</p:selectOneMenu>
The ListBacker class:
public class ListBacker<T extends AbstractEntityBase> {
// Contains the String representation of an Entity's ID (a.k.a.
// primary key) and the associated Entity object
Map<String, T> contents = new LinkedHashMap<String, T>(); // LinkedHashMap defaults to insertion-order iteration.
// These hold values (IDs), not labels (descriptions).
String selectedItem; // for SelectOne list
List<String> selectedItems; // for SelectMany list
public class ListItem {
private String value;
private String label;
public ListItem(String value, String label) {
this.value = value;
this.label = label;
}
public String getValue() {
return value;
}
public String getLabel() {
return label;
}
}
public ListBacker() {}
public ListBacker(List<T> lst) {
put(lst);
}
public void clear() {
contents.clear();
selectedItem = null;
if(selectedItems != null) {
selectedItems.clear();
}
}
public List<ListItem> getContents() {
return convert(contents);
}
public String getSelectedItem() {
return selectedItem;
}
public void setSelectedItem(String selectedItem) {
this.selectedItem = selectedItem;
}
public List<String> getSelectedItems() {
return selectedItems;
}
public void setSelectedItems(List<String> selectedItems) {
this.selectedItems = selectedItems;
}
public T getSelectedItemAsObject() {
return convert(selectedItem);
}
public List<T> getSelectedItemsAsObjects() {
return convert(selectedItems);
}
public void put(T newItem) {
contents.put(newItem.toString(), newItem);
}
public void put(List<T> newItems) {
for (T t : newItems) {
put(t);
}
}
// PROTECTED (UTILITY) METHODS
protected List<ListItem> convert(Map<String, T> maps) {
List<ListItem> lst = new ArrayList<ListItem>();
for (Entry<String, T> e : maps.entrySet()) {
lst.add(new ListItem(e.getKey(), e.getValue().desc()));
}
return lst;
}
protected List<T> convert(List<String> ids) {
List<T> lst = new ArrayList<T>();
for (String id : ids) {
lst.add(convert(id));
}
return lst;
}
protected T convert(String id) {
return contents.get(id);
}
}
I have two toString() implementations, one for JPA entities:
public abstract class AbstractEntityBase {
#Override
public final String toString() {
return String.format("%s[id=%s]", getClass().getSimpleName(), getIdForToString().toString());
}
/**
* Return the entity's ID, whether it is a field or an embedded ID class..
* #return ID Object
*/
protected abstract Object getIdForToString();
}
and one for JPA EmbeddedId's:
public abstract class CompositeKeyBase {
#Override
public final String toString() {
return String.format("%s[id=%s]", getClass().getSimpleName(), getIdForToString());
}
/**
* Supports the class's toString() method, which is required for ListBacker.
* Compile a string of all ID fields, with this format:
* fieldName=StringVALUE,field2=STRINGvAlUE2,...,fieldx=stringvalue <br />
* Recommended: start with Eclipse's "generate toString()" utility and move it to getIdForToString()
* #return a 1-to-1 String representation of the composite key
*/
public abstract String getIdForToString();
}
An example implementation of getIdForToString(), for an entity that has one Id field:
#Override
public Object getIdForToString() {
return userID;
}
An example implementation of getIdForToString(), for an EmbeddedId that has two fields:
#Override
public String getIdForToString() {
return "userID=" + userID + ",roleID=" + roleID;
}

Related

Polymorphism with swagger not working as expected

I am using springfox version 2.9.2 and swagger annotations 1.5.x. The ApiModel annotations support the discriminator, subTypes and parent attribute which are required to make polymorphism work but I am not seeing the correct apidocs generated to enable polymorphism.
Here is my annotated code.
#RestController
#RequestMapping("/api/vehicles")
public class VehicleController {
private static final Logger LOGGER = LoggerFactory.getLogger(VehicleController.class);
#PostMapping(consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE})
void post(#RequestBody Vehicle anyVehicle) {
LOGGER.info("Vehicle : {}", anyVehicle);
}
}
#ApiModel(discriminator = "type", subTypes = {Car.class, Bike.class})
public class Vehicle {
String brand;
String type;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
#ApiModel(parent = Vehicle.class)
public class Car extends Vehicle {
int noOfDoors;
boolean powerWindows;
public int getNoOfDoors() {
return noOfDoors;
}
public void setNoOfDoors(int noOfDoors) {
this.noOfDoors = noOfDoors;
}
public boolean isPowerWindows() {
return powerWindows;
}
public void setPowerWindows(boolean powerWindows) {
this.powerWindows = powerWindows;
}
}
#ApiModel(parent = Vehicle.class)
public class Bike extends Vehicle {
boolean pillion;
public boolean isPillion() {
return pillion;
}
public void setPillion(boolean pillion) {
this.pillion = pillion;
}
}
When the docs get generated is basically shows one endpoint which handles a POST request and takes in a Vehicle as the model.
Is what I am doing here supposed to work? Can someone point me to a working example of this with SpringFox that I can look at?
Support for discriminator is not available in Swagger UI yet. You can follow these issues for status updates:
Discriminator does not switch schema
subTypes not displayed in model

Add attribute dynamically to component in converter?

I wonder if should be possible to add an attribute to a component inside a converter? So inside the getAsString I would use uiComponent.addAttribute(). This seems to work 50% for me, the initial value is set, but when the converter is called later setting a new value the initial value is still retrieved.
you should not do it this way since it breaks separation of duties. you should use a bean or a scope attribute instead.
but maybe this suits:
<h:inputText value="#{bean.someValue}" converter="#{bean}">
<f:attribute name="attrName" value="#{bean.attrValue}"/>
</h:inputText>
and
#ManagedBean
public class Bean implements Converter
{
private String someValue;
private String attrValue;
#Override
public String getAsString(FacesContext context, UIComponent component, Object value)
{
attrValue = "uppercase";
return someValue.toUpperCase();
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value)
{
attrValue = "lowercase";
return value.toLowerCase();
}
public String getSomeValue()
{
return someValue;
}
public void setSomeValue(String someValue)
{
this.someValue = someValue;
}
public String getAttrValue()
{
return attrValue;
}
public void setAttrValue(String attrValue)
{
this.attrValue = attrValue;
}
}

ENTITY object in the JSF SelectOneMenu is not working

I want to get the instance of the Entity from SelectOneMenu so i can assign the entity variables to some other method. But it is pointing to null.
xhtml code
<h:selectOneMenu value="#{statusReport.projectDetID}" converter="ObjectStringConv" onchange="#{statusReport.retrieveReport()}" >
<f:selectItems value="#{statusReport.listOfProjectDetail}"
var="projectDetail" itemLabel="#{projectDetail.project} #{projectDetail.startDate} - #{projectDetail.endDate}"
itemValue="#{projectDetail}" noSelectionValue="Select the Saved Project"/>
</h:selectOneMenu>
statusReport bean
public class StatusReport implements Serializable {
private ProjectDetail projectDetID;
private List<ProjectDetail> listOfProjectDetail;
public List<ProjectDetail> getListOfProjectDetail() {
listOfProjectDetail = projectDetailFacade.findAll();
return listOfProjectDetail;
}
public void setListOfProjectDetail(List<ProjectDetail> listOfProjectDetail) {
this.listOfProjectDetail = listOfProjectDetail;
}
public ProjectDetail getProjectDetID() {
return projectDetID;
}
public void setProjectDetID(ProjectDetail projectDetID) {
this.projectDetID = projectDetID;
}
public void retrieveReport(){
System.out.println(" Processing .....");
if ( projectDetID == null )
{
System.out.println("The object from Select null");
}
else
{
System.out.println("The object from Select menu" + projectDetID.toString());
}
System.out.println("Generated Data:Completed");
}}
ProjectDetail Entity Bean
package com.jira.entity;
import java.io.Serializable;
import javax.persistence.*;
import java.util.Date;
import java.util.List;
/**
* The persistent class for the PROJECT_DETAIL database table.
*
*/
#Entity
#Table(name="PROJECT_DETAIL",schema="weeklyrep")
public class ProjectDetail implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="projectdetail_seq")
#SequenceGenerator(name="projectdetail_seq",schema="weeklyrep",sequenceName="projectdetail_seq", allocationSize=1)
#Column(name="PDETAIL_ID")
private long pdetailId;
private Boolean completed;
#Temporal( TemporalType.DATE)
#Column(name="END_DATE")
private Date endDate;
private Long project;
#Temporal( TemporalType.DATE)
#Column(name="START_DATE")
private Date startDate;
//bi-directional many-to-one association to MajorEvent
#OneToMany(mappedBy="projectDetail",cascade=CascadeType.ALL)
private List<MajorEvent> majorEvents;
//bi-directional one-to-one association to ExecSummary
#OneToOne(mappedBy="projectDetailExec",cascade=CascadeType.ALL)
private ExecSummary execSummary;
public ProjectDetail() {
}
public long getPdetailId() {
return this.pdetailId;
}
public void setPdetailId(Long pdetailId) {
this.pdetailId = pdetailId;
}
public Boolean getCompleted() {
return this.completed;
}
public void setCompleted(Boolean completed) {
this.completed = completed;
}
public Date getEndDate() {
return this.endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public long getProject() {
return this.project;
}
public void setProject(long project) {
this.project = project;
}
public Date getStartDate() {
return this.startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public List<MajorEvent> getMajorEvents() {
return this.majorEvents;
}
public void setMajorEvents(List<MajorEvent> majorEvents) {
this.majorEvents = majorEvents;
}
public ExecSummary getExecSummary() {
return execSummary;
}
public void setExecSummary(ExecSummary execSummary) {
this.execSummary = execSummary;
}
}
Converter
I don't know if it needs converter, however i don't know how code it.
package com.weeklyreport.converters;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;
import com.jira.entity.ProjectDetail;
#FacesConverter(value="ObjectStringConv")
public class ObjectStringConv implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String svalue) {
System.out.print("String version of object is:" + svalue);
return "test";
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object ovalue) {
return ovalue.toString();
}
}
Please help me figure this out. Is there a way we get instance of the entity object like this?
Your converter needs to be written that way so that it can convert between ProjectDetail and String based on an unique identifier of ProjectDetail. Usually entities have an id. You need to use this as String value. Here's a kickoff example without any trivial checks like null and instanceof:
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
// Convert ProjectDetail to its unique String representation.
ProjectDetail projectDetail = (ProjectDetail) value;
String idAsString = String.valueOf(projectDetail.getId())
return idAsString;
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
// Convert unique String representation of ProjectDetail back to ProjectDetail object.
Long id = Long.valueOf(value);
ProjectDetail projectDetail = someProjectDetailService.find(id);
return projectDetail;
}
Note that using EJBs in JSF converters (and validators) needs some hackery. See also How to inject #EJB, #PersistenceContext, #Inject, #Autowired, etc in #FacesConverter?

Struts2 Iteration of ArrayList with JavaBean

This Question may be asked in several threads...but could not fine the correct answer
a Java Bean
package com.example;
public class Document {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
An ArrayList creation of JavaBean as displayed below
package com.example;
import java.util.ArrayList;
public class classdocs {
public ArrayList getData() {
ArrayList docsmx = new ArrayList();
Document d1 = new Document();
d1.setName("user.doc");
Document d2 = new Document();
d2.setName("office.doc");
Document d3 = new Document();
d3.setName("transactions.doc");
docsmx.add(d1);
docsmx.add(d2);
docsmx.add(d3);
return docsmx;
}
}
an Action Class
package com.example;
import java.util.ArrayList;
import com.opensymphony.xwork2.ActionSupport;
public class FetchAction extends ActionSupport {
private String username;
private String message;
private ArrayList docsmx = new ArrayList();
public ArrayList getDocuments() {
return docsmx;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String execute() {
classdocs cx = new classdocs();
if( username != null) {
docsmx = cx.getData();
return "success";
} else {
message="Unable to fetch";
return "failure";
}
}
}
Jsp with Struts2 Iterator Tag
Documents uploaded by the user are:</br>
<s:iterator value="docsmx">
<s:property value="name" /></br>
</s:iterator>
Question Why the ArrayList of Bucket containing JavaBean not displayed when Iterated ...
Am I doing some thing wrong ???
with regards
karthik
Depending your version, you should either provide a getter for docsmx (preferred, pre-S2.1.mumble), or make docsmx public (not as preferred, S2.1+).
Or, based on your code, use the correct getter:
<s:iterator value="documents">
<s:property value="name" /></br>
</s:iterator>
A couple of notes: documents should likely be declared a List, not ArrayList, although in this case it almost certainly doesn't matter. It's a good habit to get in to, though, coding to an interface when the implementation doesn't matter.
I'd also consider tightening up the code a little bit:
public String execute() {
if (username == null) {
message = "Unable to fetch";
return "failure";
}
docsmx = cs.getData();
return "success";
}
This allows a more natural reading, makes it more clear what the two states are (success and failure), keeps them very distinct, and slightly shorter.

Serialize IList property on model when passed into Html.ActionLink

I'm trying to generate an Html.ActionLink with the following viewmodel:
public class SearchModel
{
public string KeyWords {get;set;}
public IList<string> Categories {get;set;}
}
To generate my link I use the following call:
#Html.ActionLink("Index", "Search", Model)
Where Model is an instance of the SearchModel
The link generated is something like this:
http://www.test.com/search/index?keywords=bla&categories=System.Collections.Generic.List
Because it obviously is only calling the ToString method on every property.
What I would like to see generate is this:
http://www.test.com/search/index?keywords=bla&categories=Cat1&categories=Cat2
Is there any way I can achieve this by using Html.ActionLink
In MVC 3 you're just out of luck because the route values are stored in a RouteValueDictionary that as the name implies uses a Dictionary internally which makes it not possible to have multiple values associated to a single key. The route values should probably be stored in a NameValueCollection to support the same behavior as the query string.
However, if you can impose some constraints on the categories names and you're able to support a query string in the format:
http://www.test.com/search/index?keywords=bla&categories=Cat1|Cat2
then you could theoretically plug it into Html.ActionLink since MVC uses TypeDescriptor which in turn is extensible at runtime. The following code is presented to demonstrate it's possible, but I would not recommend it to be used, at least without further refactoring.
Having said that, you would need to start by associating a custom type description provider:
[TypeDescriptionProvider(typeof(SearchModelTypeDescriptionProvider))]
public class SearchModel
{
public string KeyWords { get; set; }
public IList<string> Categories { get; set; }
}
The implementation for the provider and the custom descriptor that overrides the property descriptor for the Categories property:
class SearchModelTypeDescriptionProvider : TypeDescriptionProvider
{
public override ICustomTypeDescriptor GetTypeDescriptor(
Type objectType, object instance)
{
var searchModel = instance as SearchModel;
if (searchModel != null)
{
var properties = new List<PropertyDescriptor>();
properties.Add(TypeDescriptor.CreateProperty(
objectType, "KeyWords", typeof(string)));
properties.Add(new ListPropertyDescriptor("Categories"));
return new SearchModelTypeDescriptor(properties.ToArray());
}
return base.GetTypeDescriptor(objectType, instance);
}
}
class SearchModelTypeDescriptor : CustomTypeDescriptor
{
public SearchModelTypeDescriptor(PropertyDescriptor[] properties)
{
this.Properties = properties;
}
public PropertyDescriptor[] Properties { get; set; }
public override PropertyDescriptorCollection GetProperties()
{
return new PropertyDescriptorCollection(this.Properties);
}
}
Then we would need the custom property descriptor to be able to return a custom value in GetValue which is called internally by MVC:
class ListPropertyDescriptor : PropertyDescriptor
{
public ListPropertyDescriptor(string name)
: base(name, new Attribute[] { }) { }
public override bool CanResetValue(object component)
{
return false;
}
public override Type ComponentType
{
get { throw new NotImplementedException(); }
}
public override object GetValue(object component)
{
var property = component.GetType().GetProperty(this.Name);
var list = (IList<string>)property.GetValue(component, null);
return string.Join("|", list);
}
public override bool IsReadOnly { get { return false; } }
public override Type PropertyType
{
get { throw new NotImplementedException(); }
}
public override void ResetValue(object component) { }
public override void SetValue(object component, object value) { }
public override bool ShouldSerializeValue(object component)
{
throw new NotImplementedException();
}
}
And finally to prove that it works a sample application that mimics the MVC route values creation:
static void Main(string[] args)
{
var model = new SearchModel { KeyWords = "overengineering" };
model.Categories = new List<string> { "1", "2", "3" };
var properties = TypeDescriptor.GetProperties(model);
var dictionary = new Dictionary<string, object>();
foreach (PropertyDescriptor p in properties)
{
dictionary.Add(p.Name, p.GetValue(model));
}
// Prints: KeyWords, Categories
Console.WriteLine(string.Join(", ", dictionary.Keys));
// Prints: overengineering, 1|2|3
Console.WriteLine(string.Join(", ", dictionary.Values));
}
Damn, this is probably the longest answer I ever give here at SO.
with linq of course...
string.Join("", Model.Categories.Select(c=>"&categories="+c))

Resources