001/*
002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
016 *
017 * $Id: WidgetTagHandler.java 30553 2008-02-24 15:51:31Z atchertchian $
018 */
019
020package org.nuxeo.ecm.platform.forms.layout.facelets;
021
022import java.io.IOException;
023import java.io.Serializable;
024import java.util.Arrays;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028
029import javax.el.ELException;
030import javax.el.ExpressionFactory;
031import javax.el.ValueExpression;
032import javax.el.VariableMapper;
033import javax.faces.FacesException;
034import javax.faces.component.UIComponent;
035import javax.faces.view.facelets.FaceletContext;
036import javax.faces.view.facelets.FaceletHandler;
037import javax.faces.view.facelets.MetaRuleset;
038import javax.faces.view.facelets.MetaTagHandler;
039import javax.faces.view.facelets.TagAttribute;
040import javax.faces.view.facelets.TagConfig;
041import javax.faces.view.facelets.TagException;
042
043import org.apache.commons.logging.Log;
044import org.apache.commons.logging.LogFactory;
045import org.jboss.el.ValueExpressionLiteral;
046import org.nuxeo.ecm.platform.forms.layout.api.Widget;
047import org.nuxeo.ecm.platform.forms.layout.api.WidgetDefinition;
048import org.nuxeo.ecm.platform.forms.layout.service.WebLayoutManager;
049import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils;
050import org.nuxeo.runtime.api.Framework;
051
052import com.sun.faces.facelets.el.VariableMapperWrapper;
053
054/**
055 * Widget tag handler.
056 * <p>
057 * Applies {@link WidgetTypeHandler} found for given widget, in given mode and for given value.
058 *
059 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
060 */
061public class WidgetTagHandler extends MetaTagHandler {
062
063    @SuppressWarnings("unused")
064    private static final Log log = LogFactory.getLog(WidgetTagHandler.class);
065
066    protected final TagConfig config;
067
068    protected final TagAttribute widget;
069
070    /**
071     * @since 5.6
072     */
073    protected final TagAttribute name;
074
075    /**
076     * @since 5.6
077     */
078    protected final TagAttribute category;
079
080    /**
081     * @since 5.6
082     */
083    protected final TagAttribute definition;
084
085    /**
086     * @since 5.6
087     */
088    protected final TagAttribute mode;
089
090    /**
091     * @since 5.6
092     */
093    protected final TagAttribute layoutName;
094
095    /**
096     * @since 5.7
097     */
098    protected final TagAttribute resolveOnly;
099
100    protected final TagAttribute value;
101
102    protected final TagAttribute[] vars;
103
104    protected final String[] reservedVarsArray = { "id", "widget", "name", "category", "definition", "mode",
105            "layoutName", "value", "resolveOnly" };
106
107    public WidgetTagHandler(TagConfig config) {
108        super(config);
109        this.config = config;
110
111        widget = getAttribute("widget");
112        name = getAttribute("name");
113        definition = getAttribute("definition");
114        category = getAttribute("category");
115        mode = getAttribute("mode");
116        layoutName = getAttribute("layoutName");
117        resolveOnly = getAttribute("resolveOnly");
118
119        value = getAttribute("value");
120        vars = tag.getAttributes().getAll();
121
122        // additional checks
123        if (name == null && widget == null && definition == null) {
124            throw new TagException(this.tag, "At least one of attributes 'name', 'widget' "
125                    + "or 'definition' is required");
126        }
127        if (widget == null && (name != null || definition != null)) {
128            if (mode == null) {
129                throw new TagException(this.tag, "Attribute 'mode' is required when using attribute"
130                        + " 'name' or 'definition' so that the " + "widget instance " + "can be resolved");
131            }
132        }
133    }
134
135    /**
136     * Renders given widget resolving its {@link FaceletHandler} from {@link WebLayoutManager} configuration.
137     * <p>
138     * Variables exposed: {@link RenderVariables.globalVariables#value}, same variable suffixed with "_n" where n is the
139     * widget level, and {@link RenderVariables.globalVariables#document}.
140     */
141    public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, ELException {
142        // compute value name to set on widget instance in case it's changed
143        // from first computation
144        String valueName = null;
145        if (value != null) {
146            valueName = value.getValue();
147        }
148        if (ComponentTagUtils.isStrictValueReference(valueName)) {
149            valueName = ComponentTagUtils.getBareValueName(valueName);
150        }
151
152        // build handler
153        boolean widgetInstanceBuilt = false;
154        Widget widgetInstance = null;
155        if (widget != null) {
156            widgetInstance = (Widget) widget.getObject(ctx, Widget.class);
157            if (widgetInstance != null && valueName != null) {
158                widgetInstance.setValueName(valueName);
159            }
160        } else {
161            // resolve widget according to name and mode (and optional
162            // category)
163            WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class);
164
165            String modeValue = mode.getValue(ctx);
166            String layoutNameValue = null;
167            if (layoutName != null) {
168                layoutNameValue = layoutName.getValue(ctx);
169            }
170
171            if (name != null) {
172                String nameValue = name.getValue(ctx);
173                String catValue = null;
174                if (category != null) {
175                    catValue = category.getValue(ctx);
176                }
177                widgetInstance = layoutService.getWidget(ctx, nameValue, catValue, modeValue, valueName,
178                        layoutNameValue);
179                widgetInstanceBuilt = true;
180            } else if (definition != null) {
181                WidgetDefinition widgetDef = (WidgetDefinition) definition.getObject(ctx, WidgetDefinition.class);
182                if (widgetDef != null) {
183                    widgetInstance = layoutService.getWidget(ctx, widgetDef, modeValue, valueName, layoutNameValue);
184                    widgetInstanceBuilt = true;
185                }
186            }
187
188        }
189        if (widgetInstance != null) {
190            // add additional properties put on tag
191            String widgetPropertyMarker = RenderVariables.widgetVariables.widgetProperty.name() + "_";
192            List<String> reservedVars = Arrays.asList(reservedVarsArray);
193            for (TagAttribute var : vars) {
194                String localName = var.getLocalName();
195                if (!reservedVars.contains(localName)) {
196                    if (localName != null && localName.startsWith(widgetPropertyMarker)) {
197                        localName = localName.substring(widgetPropertyMarker.length());
198                    }
199                    widgetInstance.setProperty(localName, var.getValue());
200                }
201            }
202            VariableMapper orig = ctx.getVariableMapper();
203            if (widgetInstanceBuilt) {
204                // expose widget variable to the context as layout row has not done it already, and set unique id on
205                // widget before exposing it to the context
206                FaceletHandlerHelper helper = new FaceletHandlerHelper(ctx, config);
207                WidgetTagHandler.generateWidgetId(helper, widgetInstance, false);
208
209                VariableMapper vm = new VariableMapperWrapper(orig);
210                ctx.setVariableMapper(vm);
211                ExpressionFactory eFactory = ctx.getExpressionFactory();
212                ValueExpression widgetVe = eFactory.createValueExpression(widgetInstance, Widget.class);
213                vm.setVariable(RenderVariables.widgetVariables.widget.name(), widgetVe);
214                // expose widget controls too
215                for (Map.Entry<String, Serializable> ctrl : widgetInstance.getControls().entrySet()) {
216                    String key = ctrl.getKey();
217                    String name = String.format("%s_%s", RenderVariables.widgetVariables.widgetControl.name(), key);
218                    String value = String.format("#{%s.controls.%s}", RenderVariables.widgetVariables.widget.name(),
219                            key);
220                    vm.setVariable(name, eFactory.createValueExpression(ctx, value, Object.class));
221                }
222            }
223
224            try {
225                boolean resolveOnlyBool = false;
226                if (resolveOnly != null) {
227                    resolveOnlyBool = resolveOnly.getBoolean(ctx);
228                }
229
230                if (resolveOnlyBool) {
231                    nextHandler.apply(ctx, parent);
232                } else {
233                    applyWidgetHandler(ctx, parent, config, widgetInstance, value, true, nextHandler);
234                }
235            } finally {
236                ctx.setVariableMapper(orig);
237            }
238        }
239    }
240
241    public static void generateWidgetIdsRecursive(FaceletHandlerHelper helper, Widget widget) {
242        generateWidgetId(helper, widget, true);
243    }
244
245    /**
246     * @since 7.2
247     */
248    public static void generateWidgetId(FaceletHandlerHelper helper, Widget widget, boolean recursive) {
249        if (widget == null) {
250            return;
251        }
252        widget.setId(helper.generateWidgetId(widget.getName()));
253        if (recursive) {
254            Widget[] subWidgets = widget.getSubWidgets();
255            if (subWidgets != null) {
256                for (Widget subWidget : subWidgets) {
257                    generateWidgetIdsRecursive(helper, subWidget);
258                }
259            }
260        }
261    }
262
263    public static void applyWidgetHandler(FaceletContext ctx, UIComponent parent, TagConfig config, Widget widget,
264            TagAttribute value, boolean fillVariables, FaceletHandler nextHandler) throws IOException {
265        if (widget == null) {
266            return;
267        }
268        WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class);
269
270        FaceletHandlerHelper helper = new FaceletHandlerHelper(ctx, config);
271        FaceletHandler handler = layoutService.getFaceletHandler(ctx, config, widget, nextHandler);
272        if (handler == null) {
273            return;
274        }
275        if (fillVariables) {
276            // expose widget variables
277            Map<String, ValueExpression> variables = new HashMap<String, ValueExpression>();
278
279            ValueExpression valueExpr;
280            if (value == null) {
281                valueExpr = new ValueExpressionLiteral(null, Object.class);
282            } else {
283                valueExpr = value.getValueExpression(ctx, Object.class);
284            }
285
286            variables.put(RenderVariables.globalVariables.value.name(), valueExpr);
287            variables.put(
288                    String.format("%s_%s", RenderVariables.globalVariables.value.name(),
289                            Integer.valueOf(widget.getLevel())), valueExpr);
290            // document as alias to value
291            // variables.put(RenderVariables.globalVariables.document.name(),
292            // valueExpr);
293
294            FaceletHandler handlerWithVars = helper.getAliasTagHandler(widget.getTagConfigId(), variables, null,
295                    handler);
296            // apply
297            handlerWithVars.apply(ctx, parent);
298
299        } else {
300            // just apply
301            handler.apply(ctx, parent);
302        }
303    }
304
305    @Override
306    @SuppressWarnings("rawtypes")
307    protected MetaRuleset createMetaRuleset(Class type) {
308        return null;
309    }
310
311}