Having this JAXB Moxy definition, i want to optimize the HashMap output
#XmlRootElement
public class ViewElement {
private Map<StlType, ElementStyle> styles;
#XmlElementWrapper <---- must be removed thanks to #lexicore
#XmlVariableNode("type")
public Map<StlType, ElementStyle> getStyles() {
return styles;
}
private List<ViewElement> elements;
#XmlElementWrapper <---- must be removed thanks to #lexicore
#XmlElementRefs({
.....
})
public List<ViewElement> getElements(){
return elements;
}
}
My current output is :
<tab>
<styles>
<html width="100px" />
<print width="100px" />
</styles>
<elements>
<field id="code"/>
<field id="code2"/>
<listing>
....
</listing>
</elements>
</tab>
Could i have this optimized output? (even without the "e:" and "s:")
<e:tab>
<s:html width="100px" />
<s:print width="100px" />
<e:field id="code"/>
<e:field id="code2"/>
<e:listing>
....
</e:listing>
</e:tab>
OK now i have
#XmlJavaTypeAdapter(value=EventAdapter.class)
public Map<Event, String> getEvents() {
return events;
}
with
public class EventAdapter extends XmlAdapter<EventAdapter.AdaptedMap, Map<Event, String>> {
public static class AdaptedMap {
#XmlVariableNode("key")
List<AdaptedEntry> entries = new ArrayList<AdaptedEntry>();
}
public static class AdaptedEntry {
#XmlTransient
public String key;
#XmlValue
public String value;
}
#Override
public AdaptedMap marshal(Map<Event, String> map) throws Exception {
if (map==null||map.size()==0)
return null;
AdaptedMap adaptedMap = new AdaptedMap();
for(Entry<Event, String> entry : map.entrySet()) {
AdaptedEntry adaptedEntry = new AdaptedEntry();
adaptedEntry.key = entry.getKey().toString();
adaptedEntry.value = entry.getValue();
adaptedMap.entries.add(adaptedEntry);
}
return adaptedMap;
}
#Override
public Map<Event, String> unmarshal(AdaptedMap adaptedMap) throws Exception {
if (adaptedMap==null)
return null;
List<AdaptedEntry> adaptedEntries = adaptedMap.entries;
Map<Event, String> map = new HashMap<Event, String>(adaptedEntries.size());
for(AdaptedEntry adaptedEntry : adaptedEntries) {
map.put( Event.valueOf(adaptedEntry.key), adaptedEntry.value );
}
return map;
}
}
the output is also
<tab>
<html/>
<print/>
<events>
<onCellEnter>aa</onCellEnter>
</events>
</tab>
how to remove the events tag?
#XmlPath(".") remove "events" map wrapper
#XmlPath(".")
#XmlJavaTypeAdapter(value=EventAdapter.class)
public Map<Event, String> getEvents() {
return events;
}
Related
How do I define a jsf composite component properly such that its value gets bean-validated correctly in the case it contains a collection?
We have an entity that references a collection of details. Both are annotated with bean-validation-constraints. Please note the annotations at the details-property.
public class Entity implements Serializable {
#NotEmpty
private String name;
#NotEmpty
#UniqueCategory(message="category must be unique")
private List<#Valid Detail> details;
/* getters/setters */
}
public class Detail implements Serializable {
#Pattern(regexp="^[A-Z]+$")
private String text;
#NotNull
private Category category;
/* getters/setters */
}
public class Category implements Serializable {
private final int id;
private final String description;
Category(int id, String description) {
this.id = id;
this.description = description;
}
/* getters/setters */
}
public class MyConstraints {
#Target({ ElementType.TYPE, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = UniqueCategoryValidator.class)
#Documented
public static #interface UniqueCategory {
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public static class UniqueCategoryValidator implements ConstraintValidator<UniqueCategory, Collection<Detail>> {
#Override
public boolean isValid(Collection<Detail> collection, ConstraintValidatorContext context) {
if ( collection==null || collection.isEmpty() ) {
return true;
}
Set<Category> set = new HashSet<>();
collection.forEach( d-> set.add( d.getCategory() ));
return set.size() == collection.size();
}
public void initialize(UniqueCategory constraintAnnotation) {
// intentionally empty
}
}
private MyConstraints() {
// only static stuff
}
}
The entity can be edited in a jsf-form, where all the tasks concerning the details are encapsulated in a composite component, eg
<h:form id="entityForm">
<h:panelGrid columns="3">
<p:outputLabel for="#next" value="name"/>
<p:inputText id="name" value="#{entityUiController.entity.name}"/>
<p:message for="name"/>
<p:outputLabel for="#next" value="details"/>
<my:detailsComponent id="details" details="#{entityUiController.entity.details}"
addAction="#{entityUiController.addAction}"/>
<p:message for="details"/>
<f:facet name="footer">
<p:commandButton id="saveBtn" value="save"
action="#{entityUiController.saveAction}"
update="#form"/>
</f:facet>
</h:panelGrid>
</h:form>
where my:detailsComponent is defined as
<ui:component xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:p="http://primefaces.org/ui"
>
<cc:interface>
<cc:attribute name="details" required="true" type="java.lang.Iterable"/>
<cc:attribute name="addAction" required="true" method-signature="void action()"/>
</cc:interface>
<cc:implementation>
<p:outputPanel id="detailsPanel">
<ui:repeat id="detailsContainer" var="detail" value="#{cc.attrs.details}">
<p:inputText id="text" value="#{detail.text}" />
<p:message for="text"/>
<p:selectOneMenu id="category" value="#{detail.category}"
converter="#{entityUiController.categoriesConverter}"
placeholder="please select" >
<f:selectItem noSelectionOption="true" />
<f:selectItems value="#{entityUiController.categoryItems}"/>
</p:selectOneMenu>
<p:message for="category"/>
</ui:repeat>
</p:outputPanel>
<p:commandButton id="addDetailBtn" value="add" action="#{cc.attrs.addAction}"
update="detailsPanel" partialSubmit="true" process="#this detailsPanel"/>
</cc:implementation>
</ui:component>
and the EntityUiController is
#Named
#ViewScoped
public class EntityUiController implements Serializable {
private static final Logger LOG = Logger.getLogger( EntityUiController.class.getName() );
#Inject
private CategoriesBoundary categoriesBoundary;
#Valid
private Entity entity;
#PostConstruct
public void init() {
this.entity = new Entity();
}
public Entity getEntity() {
return entity;
}
public void saveAction() {
LOG.log(Level.INFO, "saved entity: {0}", this.entity );
}
public void addAction() {
this.entity.getDetails().add( new Detail() );
}
public List<SelectItem> getCategoryItems() {
return categoriesBoundary.getCategories().stream()
.map( cat -> new SelectItem( cat, cat.getDescription() ) )
.collect( Collectors.toList() );
}
public Converter<Category> getCategoriesConverter() {
return new Converter<Category>() {
#Override
public String getAsString(FacesContext context, UIComponent component, Category value) {
return value==null ? null : Integer.toString( value.getId() );
}
#Override
public Category getAsObject(FacesContext context, UIComponent component, String value) {
if ( value==null || value.isEmpty() ) {
return null;
}
try {
return categoriesBoundary.findById( Integer.valueOf(value).intValue() );
} catch (NumberFormatException e) {
throw new ConverterException(e);
}
}
};
}
}
When we now press the save-button in the above h:form, the name-inputText is validated correctly but the #NotEmpty- and #UniqueCategory-constraint on the details-property are ignored.
What am I missing?
We are on java-ee-7, payara 4.
After diving into this a bit we endet up with a solution using a backing component ValidateListComponent. It was inspired by UIValidateWholeBean and WholeBeanValidator.
That component extends from UIInput and overrides the validation-Methods to operate on a clone of the above details-collection which gets populated with the already validated values of the children-UIInput. Seems to work for now.
<ui:component ...>
<cc:interface componentType="validatedListComponent">
<cc:attribute name="addAction" required="true" method-signature="void action()"/>
</cc:interface>
<cc:implementation>
... see above ...
</cc:implementation>
</ui:component>
with the backing component defined as
#FacesComponent(value = "validatedListComponent")
#SuppressWarnings("unchecked")
public class ValidatedListComponent extends UIInput implements NamingContainer {
#Override
public String getFamily() {
return "javax.faces.NamingContainer";
}
/**
* Override {#link UIInput#processValidators(FacesContext)} to switch the order of
* validation. First validate this components children, then validate this itself.
*/
#Override
public void processValidators(FacesContext context) {
// Skip processing if our rendered flag is false
if (!isRendered()) {
return;
}
pushComponentToEL(context, this);
for (Iterator<UIComponent> i = getFacetsAndChildren(); i.hasNext(); ) {
i.next().processValidators(context);
}
if (!isImmediate()) {
Application application = context.getApplication();
application.publishEvent(context, PreValidateEvent.class, this);
executeValidate(context);
application.publishEvent(context, PostValidateEvent.class, this);
}
popComponentFromEL(context);
}
/**
* Override {#link UIInput#validate(FacesContext)} to validate a cloned collection
* instead of the submitted value.
*
* Inspired by {#link UIValidateWholeBean} and {#link WholeBeanValidator}.
*/
#Override
public void validate(FacesContext context) {
AreDetailsValidCallback callback = new AreDetailsValidCallback();
visitTree( VisitContext.createVisitContext(context)
, callback
);
if ( callback.isDetailsValid() ) {
Collection<?> clonedValue = cloneCollectionAndSetDetailValues( context );
validateValue(context, clonedValue);
}
}
/**
* private method copied from {#link UIInput#executeValidate(FacesContext)}.
* #param context
*/
private void executeValidate(FacesContext context) {
try {
validate(context);
} catch (RuntimeException e) {
context.renderResponse();
throw e;
}
if (!isValid()) {
context.validationFailed();
context.renderResponse();
}
}
private Collection<Object> cloneCollectionAndSetDetailValues(FacesContext context) {
ValueExpression collectionVE = getValueExpression("value");
Collection<?> baseCollection = (Collection<?>) collectionVE.getValue(context.getELContext());
if ( baseCollection==null ) {
return null;
}
// Visit all the components children to find their already validated values.
FindDetailValuesCallback callback = new FindDetailValuesCallback(context);
this.visitTree( VisitContext.createVisitContext(context)
, callback
);
// Clone this components value and put in all cloned details with validated values set.
try {
Collection<Object> clonedCollection = baseCollection.getClass().newInstance();
for( Entry<Object,Map<String,Object>> entry : callback.getDetailSubmittedData().entrySet() ) {
Object clonedDetail = cloneDetailAndSetValues( entry.getKey(), entry.getValue() );
clonedCollection.add( clonedDetail );
}
return clonedCollection;
} catch ( Exception e ) {
throw new ConverterException(e);
}
}
private <T> T cloneDetailAndSetValues(T detail, Map<String, Object> propertyMap) throws Exception {
T clonedDetail = clone(detail);
// check the properties we have in the detail
Map<String, PropertyDescriptor> availableProperties = new HashMap<>();
for (PropertyDescriptor propertyDescriptor : getBeanInfo(detail.getClass()).getPropertyDescriptors()) {
availableProperties.put(propertyDescriptor.getName(), propertyDescriptor);
}
// put their value (or local value) into our clone
for (Map.Entry<String, Object> propertyToSet : propertyMap.entrySet()) {
availableProperties.get(propertyToSet.getKey()).getWriteMethod().invoke(clonedDetail,
propertyToSet.getValue());
}
return clonedDetail;
}
private static <T> T clone(T object) throws Exception {
// clone an object using serialization.
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
out.writeObject(object);
byte[] bytes = byteArrayOutputStream.toByteArray();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream in = new ObjectInputStream(byteArrayInputStream);
return (T) in.readObject();
}
private class FindDetailValuesCallback implements VisitCallback {
private final FacesContext context;
private final Map<Object, Map<String, Object>> detailSubmittedData = new HashMap<>();
public FindDetailValuesCallback(final FacesContext context) {
this.context = context;
}
final Map<Object, Map<String, Object>> getDetailSubmittedData() {
return detailSubmittedData;
}
#Override
public VisitResult visit(VisitContext visitContext, UIComponent component) {
if ( isVisitorTarget(component) ) {
ValueExpression ve = component.getValueExpression("value");
Object value = ((EditableValueHolder)component).getValue();
if (ve != null) {
ValueReference vr = ve.getValueReference(context.getELContext());
String prop = (String)vr.getProperty();
Object base = vr.getBase();
Map<String, Object> propertyMap
= Optional.ofNullable( detailSubmittedData.get(base) )
.orElseGet( HashMap::new );
propertyMap.put(prop, value );
detailSubmittedData.putIfAbsent( base, propertyMap);
}
}
return ACCEPT;
}
}
private class AreDetailsValidCallback implements VisitCallback {
private boolean detailsValid;
public AreDetailsValidCallback() {
this.detailsValid = true;
}
public boolean isDetailsValid() {
return detailsValid;
}
#Override
public VisitResult visit(VisitContext visitContext, UIComponent component) {
if ( isVisitorTarget(component) ) {
if ( !((EditableValueHolder)component).isValid() ) {
this.detailsValid = false;
}
}
return ACCEPT;
}
}
private boolean isVisitorTarget( UIComponent component ) {
return component instanceof EditableValueHolder && component.isRendered()
&& component!=ValidatedListComponent.this;
}
}
UPDATE: sometimes it is a problem to obtain the ValueReference in FindDetailValuesCallback#visit. The answer given by Michele here solved that problem.
Is it possible to add validators to inputText fields from outside the h:inputText tag?
<!-- instead of this -->
<h:inputText id="field1" value="#{backingBean.field1}">
<f:validator validatorId="CustomValidator" />
</h:inputText>
<!-- something like this -->
<h:inputText id="field1" value="#{backingBean.field1}" />
<abc:validations>
<abc:validation for="field1" validator="CustomValidatorName" />
</abc:validations>
The abc:validation is a really short custom component
<cc:interface componentType="validation">
<cc:attribute name="for" />
<cc:attribute name="validator" />
</cc:interface>
<cc:implementation>
</cc:implementation>
</ui:composition>
and a faces component class
package at.sozvers.ecm.webclient.frontend.component;
import java.io.Serializable;
import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UINamingContainer;
import javax.faces.validator.Validator;
#FacesComponent("validation")
public class Validation extends UIComponentBase implements NamingContainer, Serializable {
private static final long serialVersionUID = 1L;
private String for1;
private Validator validator;
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
public String getFor() {
return for1;
}
public void setFor(String for1) {
this.for1 = for1;
}
public Validator getValidator() {
return validator;
}
public void setValidator(Validator validator) {
this.validator = validator;
}
}
I read about TagHandlers and ComponentHandlers but I have no idea how to start.
I found a solution
I created a class extending ComponentHandlerWrapper and override the onComponentPopulated method where I read the ViewMap and put a ValidatorSet for each inputText id that has to be validated. The ValidatorSet contains a Set where I write the FacesValidator names into.
public class ParentAddingComponentHandlerWrapper extends ComponentHandlerWrapper {
#Override
public void onComponentPopulated(FaceletContext ctx, UIComponent c, UIComponent parent) {
super.onComponentPopulated(ctx, c, parent);
Map<String, Object> viewMap = ctx.getFacesContext().getViewRoot().getViewMap();
if (c instanceof Validation) { // Validation is the FacesComponent class for <abc:validation>
TagAttribute uiInputName = getAttribute("for");
if (null == viewMap.get(uiInputName.getValue())) {
viewMap.put(uiInputName.getValue(), new ValidatorSet());
}
TagAttribute validator = getAttribute("validator"); // the validator attribute contains a FacesValidator name
ValidatorSet validators = (ValidatorSet) viewMap.get(uiInputName.getValue());
if (validator != null) {
validators.addValidator(validator.getValue());
}
}
}
Than I created a CustomComponent where I'm "decorating" a inputText. I also created a componentType/FacesComponent named field which I annotated with #ListenerFor(systemEventClass = PreValidateEvent.class)
Than I had to override the processEvent method where I added the FacesValidator to the UIInput component
#Override
public void processEvent(ComponentSystemEvent event) throws AbortProcessingException {
super.processEvent(event);
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
String id = getAttributeValue("id", "");
ValidatorSet validators = (ValidatorSet) viewMap.get(id);
if (validators != null) {
UIComponent findComponent = findComponent(id);
if (findComponent instanceof UIInput) {
UIInput input = (UIInput) findComponent;
List<Validator> inputValidators = Arrays.asList(input.getValidators());
for (Validator validator : validators.getValidators()) {
if (!doesUIInputContainsValidator(inputValidators, validator)) {
input.addValidator(validator);
}
}
}
}
}
private boolean doesUIInputContainsValidator(List<Validator> inputValidators, Validator validator) {
for (Validator inputValidator : inputValidators) {
if (inputValidator.getClass().isInstance(validator)) {
return true;
}
}
return false;
}
How can I pass an injected http session attribute (see below), along with other values (informe by the user) and save them using JPA?
The session attribute is correctly displayed and injected, but I need to pass it using the selected to be stored in the database (actually, it passess null).
The JSF:
<p:outputLabel value="UserID (the sessionAttribute):" for="userID" />
<p:inputText id="userID" value="#{userBean.myUser.xChave}" title="userID" />
<p:outputLabel value="Type the Reason:" for="reason" />
<p:inputText id="reason" value="#{viagensController.selected.reason}" />
<!-- updated (just the call to the action method: -->
<p:commandButton actionListener="#{viagensController.saveNew}" value="#{viagensBundle.Save}" update="display,:ViagensListForm:datalist,:growl" oncomplete="handleSubmit(xhr,status,args,ViagensCreateDialog);" />
The bean:
import java.io.Serializable;
import javax.annotation.PostConstruct;
import javax.enterprise.context.SessionScoped;
import javax.faces.context.FacesContext;
import javax.inject.Named;
#Named(value = "userBean")
#SessionScoped
public class UserBean implements Serializable {
private bean_login myUser;
public bean_login getMyUser() {
return myUser;
}
public void setMyUser(bean_login myUser) {
this.myUser = myUser;
}
#PostConstruct
public void init() {
String uid = FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("xChave").toString();
myUser = new bean_login();
myUser.setxChave(uid);
System.out.print("from init:" + myUser.toString());
}
}
The AbstractFacade:
public abstract class AbstractFacade<T> {
private Class<T> entityClass;
public AbstractFacade(Class<T> entityClass) {
this.entityClass = entityClass;
}
protected abstract EntityManager getEntityManager();
public void create(T entity) {
getEntityManager().persist(entity);
}
public void edit(T entity) {
getEntityManager().merge(entity);
}
public void remove(T entity) {
getEntityManager().remove(getEntityManager().merge(entity));
}
public T find(Object id) {{ /*impl. ommited*/ }
public List<T> findAll() {{ /*impl. ommited*/ }
public List<T> findRange(int[] range) { /*impl. ommited*/ }
public int count() { /*impl. ommited*/ }
}
The AbstractController (for the selected in JSF above and other methods):
public abstract class AbstractController<T> {
#Inject
private AbstractFacade<T> ejbFacade;
private Class<T> itemClass;
private T selected;
private Collection<T> items;
private enum PersistAction {
CREATE,
DELETE,
UPDATE
}
public AbstractController() {
}
public AbstractController(Class<T> itemClass) {
this.itemClass = itemClass;
}
public T getSelected() {
return selected;
}
// Pass in the currently selected item
public void setSelected(T selected) {
this.selected = selected;
}
protected void setEmbeddableKeys() {
}
protected void initializeEmbeddableKey() {
}
public Collection<T> getItems() {
if (items == null) {
items = this.ejbFacade.findAll();
}
return items;
}
// Pass in collection of items
public void setItems(Collection<T> items) {
this.items = items;
}
// Apply changes to an existing item to the data layer.
public void save(ActionEvent event) {
String msg = ResourceBundle.getBundle("/viagensBundle").getString(itemClass.getSimpleName() + "Updated");
persist(PersistAction.UPDATE, msg);
}
// Store a new item in the data layer.
public void saveNew(ActionEvent event) {
String msg = ResourceBundle.getBundle("/viagensBundle").getString(itemClass.getSimpleName() + "Created");
persist(PersistAction.CREATE, msg);
if (!isValidationFailed()) {
items = null; // Invalidate list of items to trigger re-query.
}
}
public void delete(ActionEvent event) {/*implementations ommited*/ }
private void persist(PersistAction persistAction, String successMessage) {
if (selected != null) {
this.setEmbeddableKeys();
try {
if (persistAction != PersistAction.DELETE) {
this.ejbFacade.edit(selected);
} else {
this.ejbFacade.remove(selected);
}
JsfUtil.addSuccessMessage(successMessage);
} catch (EJBException ex) {
String msg = "";
Throwable cause = JsfUtil.getRootCause(ex.getCause());
if (cause != null) {
if (cause instanceof ConstraintViolationException) {
ConstraintViolationException excp = (ConstraintViolationException) cause;
for (ConstraintViolation s : excp.getConstraintViolations()) {
JsfUtil.addErrorMessage(s.getMessage());
}
} else {
msg = cause.getLocalizedMessage();
if (msg.length() > 0) {
JsfUtil.addErrorMessage(msg);
} else {
JsfUtil.addErrorMessage(ex, ResourceBundle.getBundle("/Bundle").getString("PersistenceErrorOccured"));
}
}
}
} catch (Exception ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
JsfUtil.addErrorMessage(ex, ResourceBundle.getBundle("/viagensBundle").getString("PersistenceErrorOccured"));
}
}
}
// Creates a new instance of an underlying entity and assigns it to Selected property.
public T prepareCreate(ActionEvent event) {
T newItem;
try {
newItem = itemClass.newInstance();
this.selected = newItem;
initializeEmbeddableKey();
return newItem;
} catch (InstantiationException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
return null;
}
// Inform the user interface whether any validation error exist on a page.
public boolean isValidationFailed() {
return JsfUtil.isValidationFailed();
}
// Retrieve all messages as a String to be displayed on the page.
public String getComponentMessages(String clientComponent, String defaultMessage) {
return JsfUtil.getComponentMessages(clientComponent, defaultMessage);
}
}
Thanks in advance.
updated:
The ViagensController:
#Named(value = "viagensController")
#ViewScoped
public class ViagensController extends AbstractController<Viagens> implements Serializable {
//generics:passing JPA Entity class, where the 'reason' in JSF is defined
public ViagensController() {
super(Viagens.class);
}
}
Need to override the save method passing the injected http session value :
#ManagedBean(name = "riscosController")
#ViewScoped
public class RiscosController extends AbstractController<Riscos> {
#EJB
private RiscosFacade ejbFacade;
#Inject
#SessionChave
private String iSessionChave;
private String sessionChave;
private UorPosController matriculaController;
private UorPosController informanteController;
public String getSessionChave(String chave) {
if (sessionChave.isEmpty()) {
sessionChave = iSessionChave;
}
return sessionChave;
}
public void setSessionChave(String sessionChave) {
this.sessionChave = sessionChave;
}
#PostConstruct
#Override
public void init() {
super.setFacade(ejbFacade);
FacesContext context = FacesContext.getCurrentInstance();
matriculaController = context.getApplication().evaluateExpressionGet(context, "#{uorPosController}", UorPosController.class);
informanteController = context.getApplication().evaluateExpressionGet(context, "#{uorPosController}", UorPosController.class);
sessionChave = "";
}
#Override
public void saveNew(ActionEvent event) {
this.getSelected().setObs(this.getSessionChave(sessionChave));
super.saveNew(event);
}
}
I am currently implementing a orderable list using PrimeFaces' component, embedded inside a . I was able to get the list to appear properly with my items. However, when I saved the list and submitted it back to the server, the rearranged items did not get reflected in the backing bean for some reason. Since the Primefaces showcase was able to see the changes, what am I doing wrong?
XHTML Snippet:
<h:form id="confirmDialogForm">
<p:confirmDialog id="arrangeProjDialog" widgetVar="arrangeDlg" width="600"
header="Meeting Order"
appendToBody="true" message="Drag and drop to rearrange meeting order">
<p:orderList id="arrangeProjDialogList"
value="#{adminMeetingListBean.orderProjList}"
converter="#{adminMeetingListBean.rowConverter}"
var="po"
controlsLocation="left"
styleClass="wideList"
itemLabel="#{po.projectTitle}"
itemValue="#{po}"
>
<f:facet name="caption">Proposals</f:facet>
</p:orderList>
<p:commandButton value="Save" ajax="true" process="arrangeProjDialogList #this"
actionListener="#{adminMeetingListBean.updateProposalMeetingOrder}" onclick="arrangeDlg.hide();">
</p:commandButton>
<p:button value="Cancel" onclick="arrangeDlg.hide(); return false;" />
</p:confirmDialog>
</h:form>
Backing Bean:
public void updateProposalMeetingOrder() {
if (selectedMeeting != null) {
orderProjTitles.get(0);
meetingService.updateMeetingProjSequence(orderProjList, selectedMeeting.getMeetingId());
}
}
The List is a list of POJO "ProposalOrderRow" objects. This has the definition:
public class ProposalOrderRow implements Serializable {
private static final long serialVersionUID = -5012155654584965160L;
private int dispSeq;
private int appId;
private int assignmentId;
private String refNo;
private String projectTitle;
public int getDispSeq() {
return dispSeq;
}
public void setDispSeq(int dispSeq) {
this.dispSeq = dispSeq;
}
public int getAppId() {
return appId;
}
public void setAppId(int appId) {
this.appId = appId;
}
public String getRefNo() {
return refNo;
}
public void setRefNo(String refNo) {
this.refNo = refNo;
}
public String getProjectTitle() {
return projectTitle;
}
public void setProjectTitle(String projectTitle) {
this.projectTitle = projectTitle;
}
public int getAssignmentId() {
return assignmentId;
}
public void setAssignmentId(int assignmentId) {
this.assignmentId = assignmentId;
}
}
Converter:
#FacesConverter("proposalOrderRowConverter")
public class ProposalOrderRowConverter implements Converter {
private List<ProposalOrderRow> orderRows;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String newValue) {
if (newValue.isEmpty()) {
return null;
}
for (ProposalOrderRow item : orderRows) {
String refNo = item.getRefNo();
if (refNo.equals(newValue)) {
return item;
}
}
return null;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) {
return "";
}
ProposalOrderRow row = (ProposalOrderRow) value;
String output = row.getRefNo();
return output;
}
public List<ProposalOrderRow> getOrderRows() {
return orderRows;
}
public void setOrderRows(List<ProposalOrderRow> orderRows) {
this.orderRows = orderRows;
}
}
This problem is caused by appendToBody="true" in the confirm dialog. Setting it to false solved the problem.
See link here: link
I implemented a tree with an outputResource as "content" (see Downloading file from IceFaces tree).
However, when I try to download a file (I have only txt or csv files) I get the HTTP 500 Status error.
The tree structure is something like
Root
|-infoFiles
| |-A.csv
| |-B.csv
|-validateFiles
| |-C.txt
| |-D.txt
And the exception once I click on the resource is
java.io.FileNotFoundException: C:\SRC\dataFiles\998\validateFiles (Access denied)
java.io.FileInputStream.open(Native Method)
java.io.FileInputStream.<init>(FileInputStream.java:138)
java.io.FileInputStream.<init>(FileInputStream.java:97)
mx.gob.sagarpa.utilidades.FileResource.open(FileResource.java:39)
com.icesoft.faces.component.outputresource.RegisteredResource.open(OutputResource.java:474)
com.icesoft.faces.context.ResourceRegistryLocator$DynamicResourceDispatcherAdapter$DynamicResourceAdapter.open(ResourceRegistryLocator.java:117)
org.icefaces.impl.push.DynamicResourceDispatcher$ResourceServer.respond(DynamicResourceDispatcher.java:224)
org.icefaces.impl.push.DynamicResourceDispatcher$ResourceServer.handleResourceRequest(DynamicResourceDispatcher.java:201)
org.icefaces.impl.push.DynamicResourceDispatcher$Mapping.handleResourceRequest(DynamicResourceDispatcher.java:370)
org.icefaces.impl.push.DynamicResourceDispatcher.handleResourceRequest(DynamicResourceDispatcher.java:89)
org.icefaces.application.ResourceRegistry.handleResourceRequest(ResourceRegistry.java:75)
org.icefaces.impl.application.WindowScopeManager.handleResourceRequest(WindowScopeManager.java:165)
javax.faces.application.ResourceHandlerWrapper.handleResourceRequest(ResourceHandlerWrapper.java:125)
javax.faces.application.ResourceHandlerWrapper.handleResourceRequest(ResourceHandlerWrapper.java:125)
javax.faces.webapp.FacesServlet.service(FacesServlet.java:591)
org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:393)
Sometimes it happens just with csv files and sometimes even with txt
Tree.xhtml
<ice:tree id="tree"
value="#{treeBean.model}"
var="item"
hideRootNode="false"
hideNavigation="false"
>
<ice:treeNode>
<f:facet name="icon">
<ice:panelGroup style="display: inline">
<h:graphicImage value="#{item.userObject.icon}" />
</ice:panelGroup>
</f:facet>
<f:facet name="content">
<ice:panelGroup style="display: inline-block">
<ice:outputResource resource="#{item.userObject.resource}"
fileName="#{item.userObject.resource.filename}"
mimeType="#{item.userObject.resource.mimeType}"
/>
</ice:panelGroup>
</f:facet>
</ice:treeNode>
</ice:tree>
TreeBean.java
#ManagedBean
#ViewScoped
public class TreeBean implements Serializable {
private DefaultTreeModel model;
public final String openFolderImg = "./img/tree_folder_open.gif";
public final String closeFolderImg = "./img/tree_folder_close.gif";
public final String fileImg = "./img/tree_document.gif";
#ManagedProperty("#{userBean}")
private UserBean userBean;
#PostConstruct
public void init() {
// create root node with its children expanded
DefaultMutableTreeNode rootTreeNode = new DefaultMutableTreeNode();
FileResourceUserObject rootObject = new FileResourceUserObject(rootTreeNode);
rootObject.setText("Sistema de RendiciĆ³n de Cuentas");
rootObject.setExpanded(true);
rootObject.setResource(new FileResource("Sistema de RendiciĆ³n de Cuentas", null));
rootObject.setBranchContractedIcon(openFolderImg);
rootObject.setBranchExpandedIcon(closeFolderImg);
rootTreeNode.setUserObject(rootObject);
// model is accessed by the ice:tree component
model = new DefaultTreeModel(rootTreeNode);
File f = new File("./998/");
createTree(f, rootTreeNode);
}
public DefaultTreeModel getModel() {
return model;
}
public UserBean getUserBean() {
return userBean;
}
public void setUserBean(UserBean userBean) {
this.userBean = userBean;
}
private void createTree(File fileRoot, DefaultMutableTreeNode treeRoot) {
File[] files = fileRoot.listFiles();
DefaultMutableTreeNode branchNode;
for (File f : files) {
if (f.isDirectory()) {
branchNode = new DefaultMutableTreeNode();
FileResourceUserObject branchObject = new FileResourceUserObject(branchNode);
branchObject.setExpanded(false);
branchObject.setText(f.getName());
branchObject.setResource(new FileResource(f.getName(), f.getAbsolutePath()));
branchObject.setBranchContractedIcon(openFolderImg);
branchObject.setBranchExpandedIcon(closeFolderImg);
branchNode.setUserObject(branchObject);
treeRoot.add(branchNode);
createTree(f, branchNode);
}
if (f.isFile()) {
branchNode = new DefaultMutableTreeNode();
FileResourceUserObject branchObject = new FileResourceUserObject(branchNode);
branchObject.setText(f.getName());
branchObject.setResource(new FileResource(f.getName(), f.getAbsolutePath()));
branchObject.setLeaf(true);
branchObject.setLeafIcon(fileImg);
branchNode.setUserObject(branchObject);
treeRoot.add(branchNode);
}
}
return;
}
}
FileResourceUserObject.java
public class FileResourceUserObject extends IceUserObject{
private FileResource resource;
public FileResourceUserObject(DefaultMutableTreeNode wrapper) {
super(wrapper);
}
public FileResource getResource() {
return resource;
}
public void setResource(FileResource resource) {
this.resource = resource;
}
}
FileResource.java
public class FileResource implements Resource{
private String filename;
private String fileAbsolutePath;
private String mimeType;
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
public FileResource(String filename, String fileAbsolutePath) {
this.filename = filename;
this.fileAbsolutePath = fileAbsolutePath;
this.mimeType = ec.getMimeType(filename);
}
#Override
public String calculateDigest() {
return filename;
}
#Override
public InputStream open() throws IOException {
return new FileInputStream(fileAbsolutePath);
}
#Override
public Date lastModified() {
return new Date();
}
#Override
public void withOptions(Options optns) throws IOException {
}
public String getFileAbsolutePath() {
return fileAbsolutePath;
}
public void setFileAbsolutePath(String fileAbsolutePath) {
this.fileAbsolutePath = fileAbsolutePath;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public String getMimeType() {
return mimeType;
}
}
UPDATE
What I noticed was that when I get the 500 Status Error the FileNotFoundException is always pointing to the same path java.io.FileNotFoundException: C:\SRC\dataFiles\998\validateFiles (Access denied)
I'm starting to think that all my FileResources are pointing to the same path... why is this happening?
There was a report of a bug on the IceFaces page where it says that using multiple outputResource tags had a strange behavior. See this link http://jira.icefaces.org/browse/ICE-3667
I think this was happening with I had the tree and many outputResource tags.
Moreover, I also read that using the outputResource tag it creates an Object at render time (or something like that I'm very new to JSF and all the stuff related) and it was memory-cpu consuming and it was better to use a servlet to perform the download. See http://www.dantoomeysoftware.com/pencils-down/2009/09/08/dont-use-icefaces-resource-for-download-use-a-download-servlet/
So it's better to implement a servlet, you can find useful info in http://balusc.blogspot.com/2007/07/fileservlet.html and (if your are new to all this stuff) Custom download servlet