001/* 002 * (C) Copyright 2006-2007 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 018 * 019 * $Id: WidgetTypeTagHandler.java 26053 2007-10-16 01:45:43Z atchertchian $ 020 */ 021 022package org.nuxeo.ecm.platform.forms.layout.facelets; 023 024import java.io.IOException; 025import java.io.Serializable; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.HashMap; 029import java.util.List; 030import java.util.Map; 031 032import javax.el.ELException; 033import javax.el.ValueExpression; 034import javax.el.VariableMapper; 035import javax.faces.component.UIComponent; 036import javax.faces.view.facelets.ComponentConfig; 037import javax.faces.view.facelets.FaceletContext; 038import javax.faces.view.facelets.FaceletHandler; 039import javax.faces.view.facelets.TagAttribute; 040import javax.faces.view.facelets.TagConfig; 041import javax.faces.view.facelets.TagHandler; 042 043import org.apache.commons.lang.StringUtils; 044import org.apache.commons.logging.Log; 045import org.apache.commons.logging.LogFactory; 046import org.nuxeo.ecm.platform.forms.layout.api.FieldDefinition; 047import org.nuxeo.ecm.platform.forms.layout.api.Widget; 048import org.nuxeo.ecm.platform.forms.layout.api.impl.FieldDefinitionImpl; 049import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetDefinitionImpl; 050import org.nuxeo.ecm.platform.forms.layout.facelets.plugins.TemplateWidgetTypeHandler; 051import org.nuxeo.ecm.platform.forms.layout.service.WebLayoutManager; 052import org.nuxeo.ecm.platform.ui.web.binding.BlockingVariableMapper; 053import org.nuxeo.ecm.platform.ui.web.tag.handler.SetTagHandler; 054import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils; 055import org.nuxeo.runtime.api.Framework; 056 057import com.sun.faces.facelets.el.VariableMapperWrapper; 058 059/** 060 * Widget type tag handler. 061 * <p> 062 * Applies a {@link WidgetTypeHandler} resolved from a widget created for given type name and mode, and uses other tag 063 * attributes to fill the widget properties. 064 * <p> 065 * Does not handle sub widgets. 066 * 067 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 068 */ 069public class WidgetTypeTagHandler extends TagHandler { 070 071 private static final Log log = LogFactory.getLog(WidgetTypeTagHandler.class); 072 073 protected final TagConfig config; 074 075 protected final TagAttribute name; 076 077 protected final TagAttribute category; 078 079 protected final TagAttribute mode; 080 081 protected final TagAttribute value; 082 083 protected final TagAttribute field; 084 085 protected final TagAttribute fields; 086 087 protected final TagAttribute label; 088 089 protected final TagAttribute helpLabel; 090 091 protected final TagAttribute translated; 092 093 protected final TagAttribute properties; 094 095 /** 096 * @since 5.7 097 */ 098 protected final TagAttribute widgetName; 099 100 /** 101 * Convenient attribute to remove the "template" property from widget properties (and avoid stack overflow errors 102 * when using another widget type in a widget template, for compatibility code for instance). 103 * 104 * @since 5.6 105 */ 106 protected final TagAttribute ignoreTemplateProperty; 107 108 /** 109 * @since 5.6 110 */ 111 protected final TagAttribute subWidgets; 112 113 protected final TagAttribute resolveOnly; 114 115 protected final TagAttribute[] vars; 116 117 protected final String[] reservedVarsArray = { "id", "name", "category", "mode", "value", "type", "field", "fields", 118 "widgetName", "label", "helpLabel", "translated", "properties", "ignoreTemplateProperty", "subWidgets", 119 "resolveOnly" }; 120 121 public WidgetTypeTagHandler(TagConfig config) { 122 super(config); 123 this.config = config; 124 name = getRequiredAttribute("name"); 125 category = getAttribute("category"); 126 mode = getRequiredAttribute("mode"); 127 value = getAttribute("value"); 128 field = getAttribute("field"); 129 fields = getAttribute("fields"); 130 widgetName = getAttribute("widgetName"); 131 label = getAttribute("label"); 132 helpLabel = getAttribute("helpLabel"); 133 translated = getAttribute("translated"); 134 properties = getAttribute("properties"); 135 ignoreTemplateProperty = getAttribute("ignoreTemplateProperty"); 136 subWidgets = getAttribute("subWidgets"); 137 resolveOnly = getAttribute("resolveOnly"); 138 vars = tag.getAttributes().getAll(); 139 } 140 141 @Override 142 @SuppressWarnings({ "unchecked", "rawtypes" }) 143 public final void apply(FaceletContext ctx, UIComponent parent) throws IOException, ELException { 144 WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class); 145 146 // compute field definitions 147 List<FieldDefinition> fieldsValue = new ArrayList<FieldDefinition>(); 148 if (field != null) { 149 Object fieldValue = field.getObject(ctx, Object.class); 150 if (fieldValue instanceof FieldDefinition) { 151 fieldsValue.add((FieldDefinition) fieldValue); 152 } else if (fieldValue instanceof String) { 153 fieldsValue.add(new FieldDefinitionImpl(null, (String) fieldValue)); 154 } else { 155 fieldsValue.add(new FieldDefinitionImpl(null, field.getValue())); 156 } 157 } 158 if (fields != null) { 159 List resolvedfields = (List) fields.getObject(ctx, List.class); 160 for (Object item : resolvedfields) { 161 if (item instanceof FieldDefinition) { 162 fieldsValue.add((FieldDefinition) item); 163 } else if (item instanceof String) { 164 fieldsValue.add(new FieldDefinitionImpl(null, (String) item)); 165 } else { 166 log.error("Invalid field item => discard: " + item); 167 } 168 } 169 } 170 171 // build handler 172 List<String> reservedVars = Arrays.asList(reservedVarsArray); 173 Map<String, Serializable> widgetProps = new HashMap<String, Serializable>(); 174 if (properties != null) { 175 Map<String, Serializable> propertiesValue = (Map<String, Serializable>) properties.getObject(ctx, 176 Map.class); 177 if (propertiesValue != null) { 178 widgetProps.putAll(propertiesValue); 179 } 180 } 181 182 // do not propagate value the value attribute to the widget 183 // properties if field definitions should be taken into account 184 // instead 185 String widgetPropertyMarker = RenderVariables.widgetVariables.widgetProperty.name() + "_"; 186 boolean includeValueInProps = fieldsValue.isEmpty(); 187 for (TagAttribute var : vars) { 188 String localName = var.getLocalName(); 189 if ((!reservedVars.contains(localName)) || ("value".equals(localName) && includeValueInProps)) { 190 String varName = localName; 191 if (localName != null && localName.startsWith(widgetPropertyMarker)) { 192 varName = localName.substring(widgetPropertyMarker.length()); 193 } 194 widgetProps.put(varName, var.getValue()); 195 } 196 } 197 198 boolean ignoreTemplatePropValue = false; 199 if (ignoreTemplateProperty != null) { 200 ignoreTemplatePropValue = ignoreTemplateProperty.getBoolean(ctx); 201 } 202 if (ignoreTemplatePropValue) { 203 widgetProps.remove(TemplateWidgetTypeHandler.TEMPLATE_PROPERTY_NAME); 204 } 205 206 String typeValue = name.getValue(ctx); 207 String categoryValue = null; 208 if (category != null) { 209 categoryValue = category.getValue(ctx); 210 } 211 String modeValue = mode.getValue(ctx); 212 String valueName = null; 213 if (value != null) { 214 valueName = value.getValue(); 215 if (ComponentTagUtils.isStrictValueReference(valueName)) { 216 valueName = ComponentTagUtils.getBareValueName(valueName); 217 } 218 } 219 String widgetNameValue = null; 220 if (widgetName != null) { 221 widgetNameValue = widgetName.getValue(ctx); 222 } 223 String labelValue = null; 224 if (label != null) { 225 labelValue = label.getValue(ctx); 226 } 227 String helpLabelValue = null; 228 if (helpLabel != null) { 229 helpLabelValue = helpLabel.getValue(ctx); 230 } 231 Boolean translatedValue = Boolean.FALSE; 232 if (translated != null) { 233 translatedValue = Boolean.valueOf(translated.getBoolean(ctx)); 234 } 235 236 Widget[] subWidgetsValue = null; 237 if (subWidgets != null) { 238 subWidgetsValue = (Widget[]) subWidgets.getObject(ctx, Widget[].class); 239 } 240 241 // avoid double markers 242 if (widgetNameValue != null && widgetNameValue.startsWith(FaceletHandlerHelper.WIDGET_ID_PREFIX)) { 243 widgetNameValue = widgetNameValue.substring(FaceletHandlerHelper.WIDGET_ID_PREFIX.length()); 244 } 245 if (StringUtils.isBlank(widgetNameValue)) { 246 widgetNameValue = typeValue; 247 } 248 WidgetDefinitionImpl wDef = new WidgetDefinitionImpl(widgetNameValue, typeValue, labelValue, helpLabelValue, 249 translatedValue.booleanValue(), null, fieldsValue, widgetProps, null); 250 wDef.setTypeCategory(categoryValue); 251 wDef.setDynamic(true); 252 Widget widget = layoutService.createWidget(ctx, wDef, modeValue, valueName, subWidgetsValue); 253 254 if (FaceletHandlerHelper.isAliasOptimEnabled()) { 255 applyOptimized(ctx, parent, widget); 256 } else { 257 applyCompat(ctx, parent, widget); 258 } 259 } 260 261 protected void applyOptimized(FaceletContext ctx, UIComponent parent, Widget widget) 262 throws IOException, ELException { 263 // expose widget variable 264 VariableMapper orig = ctx.getVariableMapper(); 265 try { 266 BlockingVariableMapper vm = new BlockingVariableMapper(orig); 267 ctx.setVariableMapper(vm); 268 269 // set unique id on widget before exposing it to the context 270 FaceletHandlerHelper helper = new FaceletHandlerHelper(config); 271 WidgetTagHandler.generateWidgetId(ctx, helper, widget, false); 272 273 // TODO NXP-13280: retrieve widget controls from tag attributes before exposure 274 WidgetTagHandler.exposeWidgetVariables(ctx, vm, widget, null, true); 275 276 boolean resolveOnlyBool = false; 277 if (resolveOnly != null) { 278 resolveOnlyBool = resolveOnly.getBoolean(ctx); 279 } 280 if (resolveOnlyBool) { 281 nextHandler.apply(ctx, parent); 282 } else { 283 WidgetTagHandler.applyWidgetHandler(ctx, parent, config, widget, value, true, nextHandler); 284 } 285 } finally { 286 ctx.setVariableMapper(orig); 287 } 288 } 289 290 protected void applyCompat(FaceletContext ctx, UIComponent parent, Widget widget) throws IOException, ELException { 291 // expose widget variable 292 VariableMapper orig = ctx.getVariableMapper(); 293 VariableMapper vm = new VariableMapperWrapper(orig); 294 ctx.setVariableMapper(vm); 295 ValueExpression widgetVe = ctx.getExpressionFactory().createValueExpression(widget, Widget.class); 296 vm.setVariable(RenderVariables.widgetVariables.widget.name(), widgetVe); 297 vm.setVariable(RenderVariables.widgetVariables.widget.name() + "_" + widget.getLevel(), widgetVe); 298 // TODO NXP-13280: expose widget controls too when they can be 299 // retrieved from tag attributes 300 try { 301 // set unique id on widget before exposing it to the context 302 FaceletHandlerHelper helper = new FaceletHandlerHelper(config); 303 WidgetTagHandler.generateWidgetId(ctx, helper, widget, false); 304 305 boolean resolveOnlyBool = false; 306 if (resolveOnly != null) { 307 resolveOnlyBool = resolveOnly.getBoolean(ctx); 308 } 309 if (resolveOnlyBool) { 310 // NXP-12882: wrap handler in an nxu:set tag to avoid duplicate 311 // id issue when widget definition changes, as component ids 312 // can be cached and not generated-again on ajax re-render, 313 // this is a quick fix that can be optimized, as the widget 314 // variable is already exposed in the current variable mapper. 315 // Update after NXP-15050: this does not seem to be necessary 316 // anymore, could not reproduce the corresponding bug, to 317 // remove after complementary tests. 318 String setTagConfigId = widget.getTagConfigId(); 319 ComponentConfig aliasConfig = org.nuxeo.ecm.platform.ui.web.tag.handler.TagConfigFactory.createAliasTagConfig( 320 this.config, setTagConfigId, RenderVariables.widgetVariables.widget.name(), "#{widget}", "true", 321 "true", nextHandler); 322 FaceletHandler handler = new SetTagHandler(aliasConfig); 323 handler.apply(ctx, parent); 324 } else { 325 WidgetTagHandler.applyWidgetHandler(ctx, parent, config, widget, value, true, nextHandler); 326 } 327 } finally { 328 ctx.setVariableMapper(orig); 329 } 330 } 331 332}