/* * (C) Copyright 2016 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * Anahide Tchertchian */ package org.nuxeo.ecm.platform.actions.facelets; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.el.ELException; import javax.el.ExpressionFactory; import javax.el.ValueExpression; import javax.el.VariableMapper; import javax.faces.FacesException; import javax.faces.component.UIComponent; import javax.faces.view.facelets.FaceletContext; import javax.faces.view.facelets.FaceletHandler; import javax.faces.view.facelets.MetaRuleset; import javax.faces.view.facelets.MetaTagHandler; import javax.faces.view.facelets.TagAttribute; import javax.faces.view.facelets.TagAttributes; import javax.faces.view.facelets.TagConfig; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.platform.actions.Action; import org.nuxeo.ecm.platform.forms.layout.api.BuiltinWidgetModes; import org.nuxeo.ecm.platform.forms.layout.api.Widget; import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetDefinitionImpl; import org.nuxeo.ecm.platform.forms.layout.facelets.FaceletHandlerHelper; import org.nuxeo.ecm.platform.forms.layout.facelets.RenderVariables; import org.nuxeo.ecm.platform.forms.layout.facelets.WidgetTagHandler; import org.nuxeo.ecm.platform.forms.layout.service.WebLayoutManager; import org.nuxeo.ecm.platform.ui.web.binding.BlockingVariableMapper; import org.nuxeo.ecm.platform.ui.web.tag.handler.FormTagHandler; import org.nuxeo.ecm.platform.ui.web.tag.handler.TagConfigFactory; import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils; import org.nuxeo.ecm.platform.ui.web.util.FaceletDebugTracer; import org.nuxeo.runtime.api.Framework; import com.sun.faces.facelets.tag.TagAttributesImpl; /** * Tag handler rendering an action given its type, applying corresponding widget tag handler, axposing additional * variables for action templates usage. * * @since 8.2 */ public class ActionTagHandler extends MetaTagHandler { private static final Log log = LogFactory.getLog(ActionTagHandler.class); protected final TagConfig config; protected final TagAttribute action; protected final TagAttribute widgetName; protected final TagAttribute value; protected final TagAttribute mode; protected final TagAttribute addForm; protected final TagAttribute useAjaxForm; protected final TagAttribute formStyleClass; protected final TagAttribute postFilterMethod; protected final TagAttribute[] vars; protected final String[] reservedVarsArray = { "action", "widgetName", "value", "mode", "addForm", "useAjaxForm", "formStyleClass", "postFilterMethod" }; public ActionTagHandler(TagConfig config) { super(config); this.config = config; action = getRequiredAttribute("action"); widgetName = getAttribute("widgetName"); value = getRequiredAttribute("value"); mode = getAttribute("mode"); addForm = getAttribute("addForm"); useAjaxForm = getAttribute("useAjaxForm"); formStyleClass = getAttribute("formStyleClass"); postFilterMethod = getAttribute("postFilterMethod"); vars = tag.getAttributes().getAll(); } /** * Renders given widget resolving its {@link FaceletHandler} from {@link WebLayoutManager} configuration. * <p> * Variables exposed: {@link RenderVariables.globalVariables#value}, same variable suffixed with "_n" where n is the * widget level, and {@link RenderVariables.globalVariables#document}. */ public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, ELException { long start = FaceletDebugTracer.start(); Action actionInstance = null; try { if (action != null) { actionInstance = (Action) action.getObject(ctx, Action.class); } if (actionInstance == null) { return; } VariableMapper orig = ctx.getVariableMapper(); try { BlockingVariableMapper vm = new BlockingVariableMapper(orig); ctx.setVariableMapper(vm); // build corresponding widget and adjust properties String wtype = actionInstance.getType(); if (StringUtils.isBlank(wtype)) { wtype = "link"; } String wcat = "jsfAction"; String modeValue = null; if (mode != null) { modeValue = mode.getValue(ctx); } if (StringUtils.isBlank(modeValue)) { modeValue = BuiltinWidgetModes.VIEW; } Map<String, Serializable> props = new HashMap<>(); // put all action properties props.putAll(actionInstance.getProperties()); if ("template".equals(wtype)) { // avoid erasing template value from widget type configuration, and match template String templateName = "template"; String modeTemplateName = "template" + "_" + modeValue; if (BuiltinWidgetModes.VIEW.equals(modeValue) && props.containsKey(templateName)) { props.put("action_template", props.get(templateName)); } else if (props.containsKey(modeTemplateName)) { props.put("action_template", props.get(modeTemplateName)); props.remove(modeTemplateName); } props.remove(templateName); } // handle onclick StringBuilder fullOnclick = new StringBuilder(); if (BuiltinWidgetModes.VIEW.equals(modeValue) && props.containsKey("confirmMessage")) { String confirmMessage = (String) props.get("confirmMessage"); if (!StringUtils.isEmpty(confirmMessage)) { fullOnclick.append( "var message = \"#{nxu:translate(widgetProperty_confirmMessage, widgetProperty_confirmMessageArgs)}\";if (message != \"\" && !confirm(message)) {return false;};"); } } String confirm = actionInstance.getConfirm(); if (!StringUtils.isEmpty(confirm)) { fullOnclick.append(confirm).append(";"); } String onclick = (String) actionInstance.getProperties().get("onclick"); if (!StringUtils.isEmpty(onclick)) { fullOnclick.append(onclick).append(";"); } props.put("immediate", actionInstance.isImmediate()); props.put("icon", actionInstance.getIcon()); props.put("onclick", actionInstance.getConfirm()); props.put("accessKey", actionInstance.getAccessKey()); props.put("link", actionInstance.getLink()); props.put("actionId", actionInstance.getId()); props.put("action", actionInstance); if (useAjaxForm != null && !props.containsKey("useAjaxForm")) { props.put("useAjaxForm", useAjaxForm.getValue()); } String valueName = value.getValue(); String bareValueName = valueName; if (ComponentTagUtils.isStrictValueReference(valueName)) { bareValueName = ComponentTagUtils.getBareValueName(valueName); } // add filtering method if needed if (!actionInstance.isFiltered()) { // make sure variables are in the context for this filter resolution ExpressionFactory eFactory = ctx.getExpressionFactory(); ValueExpression actionVe = eFactory.createValueExpression(actionInstance, Action.class); vm.setVariable("action", actionVe); vm.addBlockedPattern("action"); String bindingValue = bareValueName; boolean bindingDone = false; if (props.containsKey("actionContextDocument")) { Object val = props.get("actionContextDocument"); if (val instanceof String && ComponentTagUtils.isStrictValueReference((String) val)) { bindingValue = ComponentTagUtils.getBareValueName((String) val); ValueExpression bindingVe = eFactory.createValueExpression(ctx, (String) val, Object.class); vm.setVariable("actionContextDocument", bindingVe); vm.addBlockedPattern("actionContextDocument"); bindingDone = true; } } if (!bindingDone) { // just bound current value to make expressions consistent vm.setVariable("actionContextDocument", value.getValueExpression(ctx, DocumentModel.class)); vm.addBlockedPattern("actionContextDocument"); } String method = null; if (postFilterMethod != null) { method = postFilterMethod.getValue(ctx); } if (StringUtils.isBlank(method)) { method = "webActions.isAvailableForDocument"; } String filterExpr = "#{" + method + "(" + bindingValue + ", action)}"; props.put("available", filterExpr); props.put("enabled", filterExpr); } else { props.put("available", actionInstance.getAvailable()); props.put("enabled", "true"); } // add all extra props passed to the tag String widgetPropertyMarker = RenderVariables.widgetVariables.widgetProperty.name() + "_"; List<String> reservedVars = Arrays.asList(reservedVarsArray); for (TagAttribute var : vars) { String localName = var.getLocalName(); if (!reservedVars.contains(localName)) { if (localName != null && localName.startsWith(widgetPropertyMarker)) { localName = localName.substring(widgetPropertyMarker.length()); } props.put(localName, var.getValue()); } } String widgetNameValue = null; if (widgetName != null) { widgetNameValue = widgetName.getValue(ctx); } if (StringUtils.isBlank(widgetNameValue)) { widgetNameValue = actionInstance.getId(); } // avoid double markers if (widgetNameValue != null && widgetNameValue.startsWith(FaceletHandlerHelper.WIDGET_ID_PREFIX)) { widgetNameValue = widgetNameValue.substring(FaceletHandlerHelper.WIDGET_ID_PREFIX.length()); } WidgetDefinitionImpl wDef = new WidgetDefinitionImpl(widgetNameValue, wtype, actionInstance.getLabel(), actionInstance.getHelp(), true, null, null, props, null); wDef.setTypeCategory(wcat); wDef.setDynamic(true); WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class); Widget widgetInstance = layoutService.createWidget(ctx, wDef, modeValue, bareValueName, null); if (widgetInstance == null) { return; } // set unique id on widget before exposing it to the context FaceletHandlerHelper helper = new FaceletHandlerHelper(config); WidgetTagHandler.generateWidgetId(ctx, helper, widgetInstance, false); // expose widget variables WidgetTagHandler.exposeWidgetVariables(ctx, vm, widgetInstance, null, false); // create widget handler TagAttributes wattrs = FaceletHandlerHelper.getTagAttributes(); wattrs = FaceletHandlerHelper.addTagAttribute(wattrs, helper.createAttribute(RenderVariables.widgetVariables.widget.name(), "#{" + RenderVariables.widgetVariables.widget.name() + "}")); wattrs = FaceletHandlerHelper.addTagAttribute(wattrs, helper.createAttribute("value", value.getValue())); TagConfig wconfig = TagConfigFactory.createTagConfig(config, config.getTagId(), wattrs, nextHandler); FaceletHandler handler = new WidgetTagHandler(wconfig); // expose ajax render props to the context String reRender = (String) props.get("ajaxReRender"); if (!StringUtils.isEmpty(reRender)) { ExpressionFactory eFactory = ctx.getExpressionFactory(); ValueExpression ve = eFactory.createValueExpression( "#{nxu:joinRender(ajaxReRender, " + reRender + ")}", String.class); vm.setVariable("ajaxReRender", ve); } // create form handler if needed boolean doAddForm = false; if (addForm != null) { doAddForm = addForm.getBoolean(ctx); } if (!doAddForm) { // check if addForm information held by the action configuration doAddForm = helper.createAttribute("addForm", String.valueOf(widgetInstance.getProperty("addForm"))) .getBoolean(ctx); } if (doAddForm) { // resolve form related attributes early boolean discard = helper.createAttribute("discardSurroundingForm", String.valueOf(widgetInstance.getProperty("discardSurroundingForm"))).getBoolean(ctx); boolean doUseAjaxForm = helper.createAttribute("useAjaxForm", String.valueOf(widgetInstance.getProperty("useAjaxForm"))).getBoolean(ctx); if (!discard || doUseAjaxForm) { List<TagAttribute> fattrs = new ArrayList<>(); if (doUseAjaxForm) { Object ajaxProp = widgetInstance.getProperty("ajaxSupport"); if (ajaxProp == null) { ajaxProp = widgetInstance.getProperty("supportAjax"); } fattrs.add(helper.createAttribute("useAjaxForm", String.valueOf(ajaxProp))); } fattrs.add(helper.createAttribute("disableMultipartForm", String.valueOf(widgetInstance.getProperty("disableMultipartForm")))); fattrs.add(helper.createAttribute("disableDoubleClickShield", String.valueOf(widgetInstance.getProperty("disableDoubleClickShield")))); fattrs.add(helper.createAttribute("styleClass", formStyleClass != null ? formStyleClass.getValue() : null)); fattrs.add(helper.createAttribute("id", widgetInstance.getId() + "_form")); TagConfig fconfig = TagConfigFactory.createTagConfig(config, config.getTagId(), new TagAttributesImpl(fattrs.toArray(new TagAttribute[] {})), handler); handler = new FormTagHandler(fconfig); } } handler.apply(ctx, parent); } finally { ctx.setVariableMapper(orig); } } finally { FaceletDebugTracer.trace(start, config.getTag(), actionInstance == null ? null : actionInstance.getId()); } } @Override @SuppressWarnings("rawtypes") protected MetaRuleset createMetaRuleset(Class type) { return null; } }