Custom Composite Component can read but can't retrieve submited value - jsf

This is how I'm rendering my composite component inside a loop, it works, but when I switch to edit mode and sumbmit new values I can't retrieve them from the InputText.
#FacesComponent("customComponent")
public class CustomComponent extends UIInput implements NamingContainer, Serializable {
private static final long serialVersionUID = 1L;
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
private UIComponent component;
private HtmlInputText inputTextValue;
#Override
public void encodeBegin(FacesContext context) throws IOException {
AttributeObject attrObject = (AttributeObject) getAttributes().get("value");
Boolean enableInput = (Boolean) getAttributes().get("enableInput");
if (attrObject.getAttributeValue() != null) {
if (attrObject.getAttributeDescriptor().getDataType() == DataTypeConstants.TEXT && enableInput) {
InputText inputText = new InputText();
inputText.setRequired(true);
inputText.setValueExpression("binding",
createValueExpression("#{searchController.myComponent}", UIComponent.class));
inputText.setId("editableTextId");
inputText.encodeAll(context);
inputText.setParent(this);
component = inputText;
} else if (attrObject.getAttributeDescriptor().getDataType() == DataTypeConstants.TEXT
&& enableInput == false) {
OutputLabel outputLabel = new OutputLabel();
outputLabel.setValue(attrObject.getAttributeValue());
outputLabel.encodeAll(context);
outputLabel.setId("nonEditatbleId");
component = outputLabel;
}
}
}
private ValueExpression createValueExpression(String valueExpression, Class<?> valueType) {
FacesContext facesContext = FacesContext.getCurrentInstance();
return facesContext.getApplication().getExpressionFactory()
.createValueExpression(facesContext.getELContext(), valueExpression, valueType);
}

Ok I think I found what caused all that mad performance problems. I did some logic inside a getter and because that getter was getting called multiple times that caused performance issues.

Related

Primefaces OrderList and Converter: getAsObject() called with "[object Object]" String

This can be seen as a continuation of my other question.
In my backend view, I have a list of some POJO. The POJO is converted to JSON with its toString() method:
BackingView.java
#Getter #Setter private List<SomePOJO> pojoList = ...
SomePojo.java
#EqualsAndHashCode(callSuper = false, of = {"id"})
public class SomePOJO implements Serializable {
private static final long serialVersionUID = 1L;
#Getter #Setter private Long id;
#Getter #Setter private Long date;
#Getter #Setter private Date name;
....
#Override
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
//making a readable string-representation of the object to display on the orderList
public String toStringOrderlistDisplay() {
return "Pojo with id " + id + "and so on... "
}
In my frontend, I want to allow the user to sort this pojo-list using Primefaces orderList :
<p:orderList id="ordList" widgetVar="ordList"
value="#{backingview.pojoList }" var="rfg"
controlsLocation="left" responsive="true" itemValue="#{rfg}"
itemLabel="#{rfg.toStringOrderlistDisplay()}" converter="#{pojoConverter}">
</p:orderList>
PojoConverter.java
#Named
#FacesConverter(value = "pojoConverter")
public class PojoConverter implements Converter {
#Override
public Pojo getAsObject(FacesContext context, UIComponent component, String value) {
if (value != null && value.trim().length() > 0) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(value, RechtsaktFallgeschichte.class);
}
catch (NumberFormatException | IOException ex) {
ex.printStackTrace();
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Conversion Error", "Not a valid Pojo."));
}
}
else { return null; }
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
SomePOJO r = (SomePOJO) value;
if (r != null) {
return r.toString();
}
else { return null; }
}
}
Converting to JSON seems to work just fine, if I print the output of the getAsString() method, everything looks as you would expect. However, when the getAsObject() method is called, the value-String-parameter always contains "[object Object]" instead of the POJOs JSON representation. What am I doing wrong here?
I don't know if it is possible to achieve what you are trying to do, but a better way (and the working one) is to pass the POJO id in the getAsString() method, and convert it back to Pojo using a cache map Map<Long, Pojo> pojoCache.
The converter should look like this:
PojoConverter.java
#Named
#FacesConverter(value = "pojoConverter")
public class PojoConverter implements Converter<Pojo> {
#Inject
private PojoService pojoService;
#Override
public Pojo getAsObject(FacesContext context, UIComponent component, String value) {
if (value != null && value.trim().length() > 0) {
return pojoService.getPojoCache().get(Long.valueOf(value));
} else {
return null;
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Pojo value) {
if (value != null) {
return String.valueOf(value.getId());
} else {
return null;
}}
}
For a more detailed explanation check this: p:orderList converter getAsObject() doesn't call Object.toString()

How to extend AjaxBehaviorEvent dispatched from #FacesComponent?

When I dispatch an ajax event from the Composite Component by using <cc:clientBehavior name="chartUpdated" event="change" targets="chartdata"/> I catch it in Facelet page by using <f:ajax event="chartUpdated" listener="#{bean.updateListener}">. And In backing bean I capture event of type AjaxBehaviorEvent.
public void updateListener(AjaxBehaviorEvent event){
...
}
I undertand that I can extend AjaxBehaviorEvent and pass within it object which has been changed. For example, Primefaces's Scheduler uses this approach:
<p:ajax event="eventMove" listener="#{scheduleView.onEventMove}" update="messages" />
And backing bean:
public void onEventMove(ScheduleEntryMoveEvent event) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Event moved", "Day delta:" + event.getDayDelta() + ", Minute delta:" + event.getMinuteDelta());
addMessage(message);
}
Is it possible to achieve the same functionality by using Composite Component together with the #FacesComponent ?
Thank you in advance!
Nice to meet you, again :)
continuing from your previous question:
Override queueEvent() to filter interesting events (changes from specific components) and postpone their enqueue to validation phase to be able to fetch converted & validated values:
#FacesComponent("rangeComponent")
public class RangeComponent extends UIInput implements NamingContainer
{
private final List<AjaxBehaviorEvent> customEvents = new ArrayList<>();
...
#Override
public void queueEvent(FacesEvent event)
{
FacesContext context = getFacesContext();
if(event instanceof AjaxBehaviorEvent)
{
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
String eventName = params.get("javax.faces.behavior.event");
Object eventSource = event.getSource();
if("change".equals(eventName) && (from.equals(eventSource) || to.equals(eventSource)))
{
customEvents.add((AjaxBehaviorEvent) event);
return;
}
}
super.queueEvent(event);
}
#Override
public void validate(FacesContext context)
{
super.validate(context);
if(from.isValid() && to.isValid())
{
for(AjaxBehaviorEvent event : customEvents)
{
SelectEvent selectEvent = new SelectEvent(this, event.getBehavior(), this.getValue());
if(event.getPhaseId().equals(PhaseId.APPLY_REQUEST_VALUES))
{
selectEvent.setPhaseId(PhaseId.PROCESS_VALIDATIONS);
}
else
{
selectEvent.setPhaseId(PhaseId.INVOKE_APPLICATION);
}
super.queueEvent(selectEvent);
}
}
}
...
}
then add the specific event listener to your managed bean:
#ManagedBean
#ViewScoped
public class RangeBean implements Serializable
{
private static final long serialVersionUID = 1L;
private String range = "01/01/2015-31/12/2015";
public void onSelect(SelectEvent event)
{
Messages.addGlobalInfo("[{0}] selected: [{1}]", event.getComponent().getId(), event.getObject());
}
public String getRange()
{
return range;
}
public void setRange(String range)
{
this.range = range;
}
}

Converter to p:selectOneMenu error JSF with EJB

My class to convert
#Entity
#Table(name = "mv_uf")
#SequenceGenerator(name = "sq_mv_uf", sequenceName = "sq_mv_uf", allocationSize = 1)
public class UF implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_mv_uf")
#Column(name = "id")
private Long id;
#Column(name = "sigla_uf")
private String siglaUF;
#Column(name = "nome_uf")
private String nomeUF;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
UF other = (UF) obj;
if (id == null) {
if (other.id != null) {
return false;
}
} else if (!id.equals(other.id)) {
return false;
}
return true;
}
//get and set
}
My converter
#FacesConverter(value = "ufConverter", forClass = UF.class)
public class UFConverter implements Converter {
#EJB
private UF uf;
private MapaEleitoralBeanLocal mapaEleitoralBean;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value != null && !value.isEmpty()) {
if (!isLong(value)) {
return "";
}
Long id = Long.parseLong(value);
uf = (UF) mapaEleitoralBean.consultarPorChavePrimaria(uf, id);
return uf;
} else {
return null;
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value != null && !"".equals(value)) {
uf = (UF) value;
return String.valueOf(uf.getId());
}
return "";
}
private boolean isLong(String valor) {
try {
long i = Long.parseLong(valor);
return true;
} catch (Exception e) {
return false;
}
}
}
My Bean
#Stateless
public class MapaEleitoralBean extends GenericDao implements MapaEleitoralBeanLocal {
#Override
public List<UF> listarTodosOsEstados() {
StringBuilder sql = new StringBuilder();
sql.append("SELECT o FROM ").append(UF.class.getSimpleName()).append(" o ");
sql.append(" order by o.nomeUF");
List<UF> listaDeTodosOsUF = new ArrayList<UF>();
listaDeTodosOsUF = (List<UF>) consultarPorQuery(sql.toString(), 0, 0);
if (listaDeTodosOsUF == null || listaDeTodosOsUF.isEmpty()) {
return null;
}
return listaDeTodosOsUF;
}
#Override
public List<Municipio> listarMunicipiosPorUF(UF uf) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT o FROM ").append(Municipio.class.getSimpleName()).append(" o ");
sql.append("WHERE o.siglaUF = '").append(uf.getSiglaUF()).append("' order by o.nomeMunicipio");
List<Municipio> listaDeMunicipiosPorUF = new ArrayList<Municipio>();
listaDeMunicipiosPorUF = (List<Municipio>) consultarPorQuery(sql.toString(), 0, 0);
if (listaDeMunicipiosPorUF == null || listaDeMunicipiosPorUF.isEmpty()) {
return null;
}
return listaDeMunicipiosPorUF;
}
}
My ManagedBean
#ManagedBean(name = "mapaEleitoralControl")
#ViewScoped
public class MapaEleitoralControl {
private static final long serialVersionUID = 1L;
#EJB
private MapaEleitoralBeanLocal mapaEleitoralBean;
private List<UF> listaUF;
private UF uf;
public MapaEleitoralControl() {
super();
}
#PostConstruct
public void init() {
listaUF = new ArrayList<UF>();
listaUF = mapaEleitoralBean.listarTodosOsEstados();
//mapaEleitoralBean.listarMunicipiosPorUF(uf);
System.out.println("pare");
}
public void handleToggle(ToggleEvent event) {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Toggled", "Visibility:" + event.getVisibility());
FacesContext.getCurrentInstance().addMessage(null, msg);
}
//get and sets
}
My xhtml
<p:column>
<p:selectOneMenu id="selectUF"
style="width:195px"
value="#{mapaEleitoralControl.uf}"
converter="ufConverter" >
<f:selectItem itemLabel="Selecione a UF
" itemValue=""
noSelectionOption="true" />
<f:selectItem value="#{mapaEleitoralControl.listaUF}"
var="uf"
itemLabel="#{uf.nomeUF}"
itemValue="#{uf}" />
</p:selectOneMenu>
</p:column>
I'm kinda new to JSF, PrimeFaces, EJB and other stuff, I'm getting this error and i have no idea why. I already look around Google and here and I can't find a solution.
00:47:57,662 WARN [org.jboss.modules] (MSC service thread 1-6) Failed to define class br.com.maxvoto.converter.EntityConverter in Module "deployment.maxvoto-ear.ear.maxvoto-ejb.jar:main" from Service Module Loader: java.lang.LinkageError: Failed to link br/com/maxvoto/converter/EntityConverter (Module "deployment.maxvoto-ear.ear.maxvoto-ejb.jar:main" from Service Module Loader)
at org.jboss.modules.ModuleClassLoader.defineClass(ModuleClassLoader.java:487) [jboss-modules.jar:1.3.3.Final-redhat-1]
at org.jboss.modules.ModuleClassLoader.loadClassLocal(ModuleClassLoader.java:277) [jboss-modules.jar:1.3.3.Final-redhat-1]
at org.jboss.modules.ModuleClassLoader$1.loadClassLocal(ModuleClassLoader.java:92) [jboss-modules.jar:1.3.3.Final-redhat-1]
at org.jboss.modules.Module.load
ModuleClass(Module.java:568) [jboss-modules.jar:1.3.3.Final-redhat-1]
....
You should not use use #EJB annotation. Injection only works in managed beans. Get the target component using InitialContext. Alternative, making your converter as beans that can inject an EJB.
#ManagedBean
#RequestScoped
public class UFConverter implements Converter {
#EJB
private UF uf;
// ...
}
JSF allow the container to inject references to container managed resources into a managed bean instance before it is made accessible to the JSF application. Only beans declared to be in request, session, or application scope are eligble for resource injection.
See also: How to inject #EJB, #PersistenceContext, #Inject, #Autowired, etc in #FacesConverter?

JSF: No validator could be found for type when adding a converter and a validator to a p:selectOneMenu

I'm currently encountering the problem specified above when trying to add a converter and a validator to a single field. I know how these 2 works because I've used them before, but this is the first time I've tried both of them on a single field. Here are my codes:
<p:selectOneMenu id="bussProgram"
value="#{signupWizardBean.subscription.businessProgram}">
<f:converter binding="#{membershipProgramConverter}"></f:converter>
<f:validator binding="#{dropdownValidator}"></f:validator>
<f:selectItems value="#{signupWizardBean.membershipPrograms}"
var="plan" itemValue="#{plan}"
itemLabel="#{plan.description}" />
</p:selectOneMenu>
Converter
#Named
#ApplicationScoped
public class MembershipProgramConverter implements Converter {
#PersistenceContext
private transient EntityManager em;
#Inject
private Logger log;
#Override
public Object getAsObject(FacesContext ctx, UIComponent component,
String value) {
if (StringUtils.isBlank(value) || value.equals("0"))
return null;
return em.find(MembershipProgram.class, new Long(value));
}
#Override
public String getAsString(FacesContext fc, UIComponent uic, Object o) {
MembershipProgram mp = (MembershipProgram) value;
return mp.getId() != null ? String.valueOf(mp.getId()) : null;
}
}
Validator:
#Named
#ApplicationScoped
public class DropdownValidator implements Validator, Serializable {
private static final long serialVersionUID = -456545939475878299L;
#Inject
private ResourceBundle bundle;
#Inject
private FacesContext facesContext;
#Inject
private Logger log;
#Override
public void validate(FacesContext context, UIComponent component,
Object value) throws ValidatorException {
log.debug("[dropship-web] validating value={}", value);
if (value == null) {
FacesMessage msg = new FacesMessage(
bundle.getString("error.requiredField"),
bundle.getString("error.requiredField"));
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
facesContext.addMessage("promoCode", msg);
throw new ValidatorException(msg);
}
}
}
Entity Class:
#Entity
#Table(name = "CRM_MEMBERSHIP_PROGRAMS", uniqueConstraints = #UniqueConstraint(columnNames = { "code" }))
#SequenceGenerator(name = "ID_GENERATOR", sequenceName = "CRM_MEMBERSHIP_PROGRAM_SEQ")
public class MembershipProgram extends BaseEntity {
private static final long serialVersionUID = -7796298586810386239L;
#Size(max = 25)
#Column(name = "CODE", nullable = false)
private String code;
#Size(max = 100)
#Column(name = "DESCRIPTION", nullable = true)
private String description;
public MembershipProgram() {
}
public MembershipProgram(long id, String description) {
setId(id);
this.description = description;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
#Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((code == null) ? 0 : code.hashCode());
result = prime * result
+ ((description == null) ? 0 : description.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
MembershipProgram other = (MembershipProgram) obj;
if (code == null) {
if (other.code != null)
return false;
} else if (!code.equals(other.code))
return false;
if (description == null) {
if (other.description != null)
return false;
} else if (!description.equals(other.description))
return false;
return true;
}
}
Note that on debug: both the MembershipProgramConverter.getAsObject/getAsString, DropdownValidator.validate methods are called.
I also found out that with or without selected item from the dropdown list, I'm encountering:
.validation.UnexpectedTypeException: HV000030: No validator could be found for type: com.czetsuya.models.membership.MembershipProgram.
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for type: com.czetsuya.models.membership.MembershipProgram.
Finally I was able to figure out the problem, I've added #Size annotation on the target entity, just removed the #Size:
#Size(max = 25)
#OneToOne(optional = false)
#JoinColumn(name = "BUSINESS_PROGRAM", nullable = false)
private MembershipProgram businessProgram;
When I removed the #Size, the problem was solved but I have encountered a new one, on submit it the form always throw:
Validation Error: Value is not valid
To fixed the Value is not valid, make sure that you have properly implemented Model.equals method. My fault was that I rely on eclipse generate hashcode and equals feature, I failed to notice that it calls the equals method from my BaseEntity.

JSF - custom NavigationHandler outcome values invalid?

I wrote myself a custom NavigationHandler very similar to following one but just using a stack to save the history:
http://jsfatwork.irian.at/book_de/custom_component.html#!idx:/custom_component.html:fig:backnavigationhandler-code
public class HistoryNavigationHandler extends NavigationHandler
{
private final NavigationHandler navigationHandler;
private final Stack<String> outcomes;
public HistoryNavigationHandler(final NavigationHandler navigationHandler)
{
this.navigationHandler = navigationHandler;
this.outcomes = new Stack<String>();
}
#Override
public void handleNavigation(final FacesContext context, final String fromAction, final String outcome)
{
if (outcome != null)
{
if (outcome.equals("back"))
{
final String lastViewId = this.outcomes.pop();
final ViewHandler viewHandler = context.getApplication().getViewHandler();
final UIViewRoot viewRoot = viewHandler.createView(context, lastViewId);
context.setViewRoot(viewRoot);
context.renderResponse();
return;
}
else
{
this.outcomes.push(context.getViewRoot().getViewId());
}
}
this.navigationHandler.handleNavigation(context, fromAction, outcome);
}
}
Registering this one in the faces-config.xml:
<navigation-handler>
package.HistoryNavigationHandler
</navigation-handler>
Results in following log warning and a message where a previously working link was present:
Warning: jsf.outcome.target.invalid.navigationhandler.type
Something like: this link is disabled because a navigation case could not be matched
What is the problem?
Since JSF 2, the NavigationHandler has been replaced by ConfigurableNavigationHandler. All JSF 2 specific tags/components like <h:link> and so on are relying on it. The NavigationHandler is kept for backwards compatibility.
Here's a kickoff example how to properly extend ConfigurableNavigationHandler:
public class HistoryNavigationHandler extends ConfigurableNavigationHandler {
private NavigationHandler wrapped;
public HistoryNavigationHandler(NavigationHandler wrapped) {
this.wrapped = wrapped;
}
#Override
public void handleNavigation(FacesContext context, String from, String outcome) {
// TODO: Do your job here.
wrapped.handleNavigation(context, from, outcome);
}
#Override
public NavigationCase getNavigationCase(FacesContext context, String fromAction, String outcome) {
return (wrapped instanceof ConfigurableNavigationHandler)
? ((ConfigurableNavigationHandler) wrapped).getNavigationCase(context, fromAction, outcome)
: null;
}
#Override
public Map<String, Set<NavigationCase>> getNavigationCases() {
return (wrapped instanceof ConfigurableNavigationHandler)
? ((ConfigurableNavigationHandler) wrapped).getNavigationCases()
: null;
}
}

Resources