/* * Copyright 2011 PrimeFaces Extensions. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * $Id: MasterDetail.java 555 2011-12-08 20:52:00Z Zoigln@googlemail.com $ */ package org.primefaces.extensions.component.masterdetail; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.el.MethodExpression; import javax.el.ValueExpression; import javax.faces.FacesException; import javax.faces.application.ResourceDependency; import javax.faces.component.UIComponent; import javax.faces.component.UIComponentBase; import javax.faces.context.FacesContext; import javax.faces.context.PartialViewContext; import javax.faces.event.AbortProcessingException; import javax.faces.event.ComponentSystemEvent; import javax.faces.event.ListenerFor; import javax.faces.event.PostRestoreStateEvent; import org.apache.commons.lang.StringUtils; import org.primefaces.util.Constants; /** * <code>MasterDetail</code> component. * * @author Oleg Varaksin / last modified by $Author: Zoigln@googlemail.com $ * @version $Revision: 555 $ */ @ListenerFor(systemEventClass = PostRestoreStateEvent.class) @ResourceDependency(library = "primefaces-extensions", name = "primefaces-extensions.css") public class MasterDetail extends UIComponentBase { public static final String COMPONENT_FAMILY = "org.primefaces.extensions.component"; private static final String DEFAULT_RENDERER = "org.primefaces.extensions.component.MasterDetailRenderer"; private static final String OPTIMIZED_PACKAGE = "org.primefaces.extensions.component."; public static final String CONTEXT_VALUE_VALUE_EXPRESSION = "mdContextValueVE"; public static final String SELECTED_LEVEL_VALUE_EXPRESSION = "selectedLevelVE"; public static final String SELECTED_STEP_VALUE_EXPRESSION = "selectedStepVE"; public static final String CONTEXT_VALUES = "mdContextValues"; public static final String SKIP_PROCESSING = "mdSkipProcessing"; public static final String SELECT_DETAIL_REQUEST = "_selectDetailRequest"; public static final String CURRENT_LEVEL = "_currentLevel"; public static final String SELECTED_LEVEL = "_selectedLevel"; public static final String SELECTED_STEP = "_selectedStep"; public static final String CURRENT_CONTEXT_VALUE = "_curContextValue"; public static final String SKIP_PROCESSING_REQUEST = "_skipProcessing"; private MasterDetailLevel detailLevelToProcess; private MasterDetailLevel detailLevelToGo; private int levelPositionToProcess = -1; private int levelCount = -1; /** * Properties that are tracked by state saving. * * @author Oleg Varaksin / last modified by $Author: Zoigln@googlemail.com $ * @version $Revision: 555 $ */ protected enum PropertyKeys { level, flow, flowListener, showBreadcrumb, style, styleClass; private String toString; PropertyKeys(final String toString) { this.toString = toString; } PropertyKeys() { } @Override public String toString() { return ((this.toString != null) ? this.toString : super.toString()); } } public MasterDetail() { setRendererType(DEFAULT_RENDERER); } @Override public String getFamily() { return COMPONENT_FAMILY; } public int getLevel() { return (Integer) getStateHelper().eval(PropertyKeys.level, 1); } public void setLevel(final int level) { setAttribute(PropertyKeys.level, level); } public Object getFlow() { return getStateHelper().eval(PropertyKeys.flow, null); } public void setFlow(final Object flow) { setAttribute(PropertyKeys.flow, flow); } public MethodExpression getFlowListener() { return (MethodExpression) getStateHelper().eval(PropertyKeys.flowListener, null); } public void setFlowListener(final MethodExpression flowListener) { setAttribute(PropertyKeys.flowListener, flowListener); } public boolean isShowBreadcrumb() { return (Boolean) getStateHelper().eval(PropertyKeys.showBreadcrumb, true); } public void setShowBreadcrumb(final boolean showBreadcrumb) { setAttribute(PropertyKeys.showBreadcrumb, showBreadcrumb); } public String getStyle() { return (String) getStateHelper().eval(PropertyKeys.style, null); } public void setStyle(final String style) { setAttribute(PropertyKeys.style, style); } public String getStyleClass() { return (String) getStateHelper().eval(PropertyKeys.styleClass, null); } public void setStyleClass(final String styleClass) { setAttribute(PropertyKeys.styleClass, styleClass); } public void setAttribute(final PropertyKeys property, final Object value) { getStateHelper().put(property, value); @SuppressWarnings("unchecked") List<String> setAttributes = (List<String>) this.getAttributes().get("javax.faces.component.UIComponentBase.attributesThatAreSet"); if (setAttributes == null) { final String cname = this.getClass().getName(); if (cname != null && cname.startsWith(OPTIMIZED_PACKAGE)) { setAttributes = new ArrayList<String>(6); this.getAttributes().put("javax.faces.component.UIComponentBase.attributesThatAreSet", setAttributes); } } if (setAttributes != null && value == null) { final String attributeName = property.toString(); final ValueExpression ve = getValueExpression(attributeName); if (ve == null) { setAttributes.remove(attributeName); } else if (!setAttributes.contains(attributeName)) { setAttributes.add(attributeName); } } } @Override public void processEvent(ComponentSystemEvent event) throws AbortProcessingException { super.processEvent(event); FacesContext fc = FacesContext.getCurrentInstance(); if (!(event instanceof PostRestoreStateEvent) || !isSelectDetailRequest(fc)) { return; } String clienId = this.getClientId(fc); PartialViewContext pvc = fc.getPartialViewContext(); // process and update the MasterDetail component automatically if (!isSkipProcessing(fc)) { pvc.getExecuteIds().add(clienId); } pvc.getRenderIds().add(clienId); MasterDetailLevel mdl = getDetailLevelToProcess(fc); Object contextValue = getContextValueFromFlow(fc, mdl); String contextVar = mdl.getContextVar(); if (StringUtils.isNotBlank(contextVar) && contextValue != null) { Map<String, Object> requestMap = fc.getExternalContext().getRequestMap(); requestMap.put(contextVar, contextValue); } } @Override public void processDecodes(final FacesContext fc) { if (!isSelectDetailRequest(fc)) { super.processDecodes(fc); } else { getDetailLevelToProcess(fc).processDecodes(fc); } } @Override public void processValidators(final FacesContext fc) { if (!isSelectDetailRequest(fc)) { super.processValidators(fc); } else { getDetailLevelToProcess(fc).processValidators(fc); } } @Override public void processUpdates(final FacesContext fc) { if (!isSelectDetailRequest(fc)) { super.processUpdates(fc); } else { getDetailLevelToProcess(fc).processUpdates(fc); } } public MasterDetailLevel getDetailLevelToProcess(final FacesContext fc) { if (detailLevelToProcess == null) { initDataForLevels(fc); } return detailLevelToProcess; } public MasterDetailLevel getDetailLevelToGo(final FacesContext fc) { if (detailLevelToGo != null) { return detailLevelToGo; } final String strSelectedLevel = fc.getExternalContext().getRequestParameterMap().get(getClientId(fc) + SELECTED_LEVEL); final String strSelectedStep = fc.getExternalContext().getRequestParameterMap().get(getClientId(fc) + SELECTED_STEP); // selected level != null if (strSelectedLevel != null) { int selectedLevel = Integer.valueOf(strSelectedLevel); detailLevelToGo = getDetailLevelByLevel(selectedLevel); if (detailLevelToGo != null) { return detailLevelToGo; } throw new FacesException("MasterDetailLevel for selected level = " + selectedLevel + " not found."); } int step; if (strSelectedStep != null) { // selected step != null step = Integer.valueOf(strSelectedStep); } else { // selected level and selected step are null ==> go to the next level step = 1; } detailLevelToGo = getDetailLevelByStep(step); return detailLevelToGo; } public MasterDetailLevel getDetailLevelByLevel(final int level) { for (UIComponent child : getChildren()) { if (child instanceof MasterDetailLevel) { MasterDetailLevel mdl = (MasterDetailLevel) child; if (mdl.getLevel() == level) { return mdl; } } } return null; } public boolean isSelectDetailRequest(final FacesContext fc) { return fc.getPartialViewContext().isAjaxRequest() && fc.getExternalContext().getRequestParameterMap().containsKey(getClientId(fc) + SELECT_DETAIL_REQUEST); } public void updateModel(final FacesContext fc, final MasterDetailLevel mdlToGo) { final int levelToGo = mdlToGo.getLevel(); ValueExpression levelVE = this.getValueExpression(PropertyKeys.level.toString()); if (levelVE != null) { // update "level" levelVE.setValue(fc.getELContext(), levelToGo); getStateHelper().remove(PropertyKeys.level); } // get UICommand caused this ajax request final String source = fc.getExternalContext().getRequestParameterMap().get(Constants.PARTIAL_SOURCE_PARAM); MasterDetailLevel mdl = getDetailLevelToProcess(fc); // get resolved context value Object contextValue = null; @SuppressWarnings("unchecked") Map<String, Object> contextValues = (Map<String, Object>) mdl.getAttributes().get(CONTEXT_VALUES); if (contextValues != null) { contextValue = contextValues.get("contextValue_" + source); } if (contextValue != null) { // update current context value for corresponding MasterDetailLevel mdlToGo.getAttributes().put(getClientId(fc) + CURRENT_CONTEXT_VALUE, contextValue); // update "flow" ValueExpression flowVE = this.getValueExpression(PropertyKeys.flow.toString()); if (flowVE != null) { Class flowType = flowVE.getType(fc.getELContext()); if (!flowType.isArray()) { // we can also check Collection.class.isAssignableFrom(flowType), but a support of collection is too complicate // by reason of lack in Java Generics. The type of elements in a collection is not accessible at runtime. throw new FacesException("Type of the 'flow' attribute must be an Array."); } Class elementType = flowType.getComponentType(); if (!FlowLevel.class.isAssignableFrom(elementType)) { throw new FacesException("Elements in the 'flow' array must implement 'FlowLevel' interface."); } try { FlowLevel newFlowLevel = (FlowLevel) elementType.newInstance(); newFlowLevel.setLevel(levelToGo); newFlowLevel.setContextValue(contextValue); FlowLevel[] newFlow; Object objFlow = flowVE.getValue(fc.getELContext()); if (objFlow == null) { newFlow = (FlowLevel[]) Array.newInstance(elementType, 1); newFlow[0] = newFlowLevel; } else { List<FlowLevel> listFlow = new ArrayList<FlowLevel>(); listFlow.add(newFlowLevel); for (FlowLevel fl : (FlowLevel[]) objFlow) { if (fl.getLevel() != levelToGo) { listFlow.add(fl); } } newFlow = listFlow.toArray((FlowLevel[]) Array.newInstance(elementType, listFlow.size())); } flowVE.setValue(fc.getELContext(), newFlow); getStateHelper().remove(PropertyKeys.flow); } catch (Exception e) { throw new FacesException("Update of 'flow' array has failed."); } } } } public Object getContextValueFromFlow(final FacesContext fc, final MasterDetailLevel mdl) { // try to get context value from internal storage Object contextValue = mdl.getAttributes().get(this.getClientId(fc) + MasterDetail.CURRENT_CONTEXT_VALUE); if (contextValue != null) { return contextValue; } // try to get context value from external "flow" state FlowLevel[] flowLevels = (FlowLevel[]) this.getFlow(); if (flowLevels != null && flowLevels.length > 0) { final int level = mdl.getLevel(); for (FlowLevel fl : flowLevels) { if (fl.getLevel() == level) { return fl.getContextValue(); } } } return null; } public void resetCalculatedValues() { detailLevelToProcess = null; detailLevelToGo = null; levelPositionToProcess = -1; levelCount = -1; } private void initDataForLevels(final FacesContext fc) { final String strCurrentLevel = fc.getExternalContext().getRequestParameterMap().get(getClientId(fc) + CURRENT_LEVEL); if (strCurrentLevel == null) { throw new FacesException("Current level is missing in request."); } int currentLevel = Integer.valueOf(strCurrentLevel); int count = 0; for (UIComponent child : getChildren()) { if (child instanceof MasterDetailLevel) { MasterDetailLevel mdl = (MasterDetailLevel) child; count++; if (detailLevelToProcess == null && mdl.getLevel() == currentLevel) { detailLevelToProcess = mdl; levelPositionToProcess = count; } } } levelCount = count; if (detailLevelToProcess == null) { throw new FacesException("Current MasterDetailLevel to process not found."); } } private boolean isSkipProcessing(final FacesContext fc) { return fc.getExternalContext().getRequestParameterMap().containsKey(getClientId(fc) + SKIP_PROCESSING_REQUEST); } private MasterDetailLevel getDetailLevelByStep(final int step) { int levelPositionToGo = getLevelPositionToProcess() + step; if (levelPositionToGo < 1) { levelPositionToGo = 1; } else if (levelPositionToGo > getLevelCount()) { levelPositionToGo = getLevelCount(); } int pos = 0; for (UIComponent child : getChildren()) { if (child instanceof MasterDetailLevel) { MasterDetailLevel mdl = (MasterDetailLevel) child; pos++; if (pos == levelPositionToGo) { return mdl; } } } // should not happen return null; } private int getLevelPositionToProcess() { if (levelPositionToProcess == -1) { initDataForLevels(FacesContext.getCurrentInstance()); } return levelPositionToProcess; } private int getLevelCount() { if (levelCount == -1) { initDataForLevels(FacesContext.getCurrentInstance()); } return levelCount; } }