001/*
002 * (C) Copyright 2015 Nuxeo SA (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-2.1.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 *     Anahide Tchertchian
016 */
017package org.nuxeo.ecm.web.resources.jsf.handler;
018
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Collection;
023import java.util.List;
024
025import javax.faces.component.UIComponent;
026import javax.faces.component.UIOutput;
027import javax.faces.view.facelets.ComponentConfig;
028import javax.faces.view.facelets.ComponentHandler;
029import javax.faces.view.facelets.FaceletContext;
030import javax.faces.view.facelets.FaceletHandler;
031import javax.faces.view.facelets.MetaRuleset;
032import javax.faces.view.facelets.MetaTagHandler;
033import javax.faces.view.facelets.TagAttribute;
034import javax.faces.view.facelets.TagConfig;
035
036import org.apache.commons.lang.StringUtils;
037import org.apache.commons.logging.Log;
038import org.apache.commons.logging.LogFactory;
039import org.nuxeo.ecm.platform.ui.web.tag.handler.LeafFaceletHandler;
040import org.nuxeo.ecm.platform.ui.web.tag.handler.TagConfigFactory;
041import org.nuxeo.ecm.web.resources.api.Resource;
042import org.nuxeo.ecm.web.resources.api.ResourceContextImpl;
043import org.nuxeo.ecm.web.resources.api.ResourceType;
044import org.nuxeo.ecm.web.resources.api.service.WebResourceManager;
045import org.nuxeo.ecm.web.resources.jsf.ResourceBundleRenderer;
046import org.nuxeo.runtime.api.Framework;
047
048import com.sun.faces.facelets.tag.TagAttributeImpl;
049import com.sun.faces.facelets.tag.TagAttributesImpl;
050import com.sun.faces.facelets.tag.jsf.html.ScriptResourceHandler;
051import com.sun.faces.facelets.tag.jsf.html.StylesheetResourceHandler;
052import com.sun.faces.facelets.tag.ui.IncludeHandler;
053
054/**
055 * Tag handler for resource bundles, resolving early resources that need to be included at build time (e.g JSF and XHTML
056 * resources for now).
057 *
058 * @since 7.4
059 */
060public class ResourceBundleHandler extends MetaTagHandler {
061
062    private static final Log log = LogFactory.getLog(ResourceBundleHandler.class);
063
064    protected final TagConfig config;
065
066    protected final TagAttribute name;
067
068    protected final TagAttribute type;
069
070    protected final TagAttribute items;
071
072    protected final TagAttribute target;
073
074    protected final TagAttribute[] vars;
075
076    protected final ResourceType[] handledTypesArray = { ResourceType.css, ResourceType.js, ResourceType.jsfcss,
077            ResourceType.jsfjs, ResourceType.html, ResourceType.xhtml, ResourceType.xhtmlfirst };
078
079    public ResourceBundleHandler(TagConfig config) {
080        super(config);
081        this.config = config;
082        name = getAttribute("name");
083        items = getAttribute("items");
084        type = getAttribute("type");
085        target = getAttribute("target");
086        vars = tag.getAttributes().getAll();
087    }
088
089    @Override
090    @SuppressWarnings("rawtypes")
091    protected MetaRuleset createMetaRuleset(Class type) {
092        return null;
093    }
094
095    @SuppressWarnings("unchecked")
096    @Override
097    public void apply(FaceletContext ctx, UIComponent parent) throws IOException {
098        String typeValue = null;
099        if (type != null) {
100            typeValue = type.getValue(ctx);
101        }
102        ResourceType rtype = resolveType(typeValue);
103        if (rtype == null) {
104            log.error(String.format("Unsupported type '%s' on tag nxr:resourceBundle at %s", typeValue,
105                    tag.getLocation()));
106            return;
107        }
108
109        List<String> bundles = new ArrayList<String>();
110        if (name != null) {
111            String bundleName = name.getValue(ctx);
112            if (!StringUtils.isBlank(bundleName)) {
113                bundles.add(bundleName);
114            }
115        }
116        if (items != null) {
117            Object itemsValue = items.getObject(ctx);
118            if (itemsValue instanceof Collection) {
119                bundles.addAll((Collection<String>) itemsValue);
120            } else if (itemsValue instanceof Object[]) {
121                bundles.addAll(Arrays.asList((String[]) itemsValue));
122            } else if (itemsValue instanceof String) {
123                bundles.add((String) itemsValue);
124            } else {
125                log.error(String.format("Unsupported value '%s' for attribute 'items' on tag nxr:resourceBundle at %s",
126                        itemsValue, tag.getLocation()));
127            }
128        }
129
130        if (bundles.isEmpty()) {
131            return;
132        }
133
134        String targetValue = null;
135        if (target != null) {
136            targetValue = target.getValue(ctx);
137        }
138
139        WebResourceManager wrm = Framework.getService(WebResourceManager.class);
140        LeafFaceletHandler leaf = new LeafFaceletHandler();
141        if (rtype == ResourceType.any) {
142            for (String bundle : bundles) {
143                String cssTarget = targetValue;
144                String jsTarget = targetValue;
145                String htmlTarget = targetValue;
146                if (vars != null) {
147                    for (TagAttribute var : vars) {
148                        if ("target_css".equalsIgnoreCase(var.getLocalName())) {
149                            cssTarget = var.getValue(ctx);
150                        } else if ("target_js".equalsIgnoreCase(var.getLocalName())) {
151                            jsTarget = var.getValue(ctx);
152                        } else if ("target_html".equalsIgnoreCase(var.getLocalName())) {
153                            htmlTarget = var.getValue(ctx);
154                        }
155                    }
156                }
157                // first include handlers that match JSF resources
158                applyBundle(ctx, parent, wrm, bundle, ResourceType.jsfcss, cssTarget, leaf);
159                applyBundle(ctx, parent, wrm, bundle, ResourceType.jsfjs, jsTarget, leaf);
160                // then include xhtmlfirst templates
161                applyBundle(ctx, parent, wrm, bundle, ResourceType.xhtmlfirst, null, leaf);
162                // then let other resources (css, js, html) be processed by the component at render time
163                applyBundle(ctx, parent, wrm, bundle, ResourceType.css, cssTarget, nextHandler);
164                applyBundle(ctx, parent, wrm, bundle, ResourceType.js, jsTarget, nextHandler);
165                applyBundle(ctx, parent, wrm, bundle, ResourceType.html, htmlTarget, nextHandler);
166                // then include xhtml templates
167                applyBundle(ctx, parent, wrm, bundle, ResourceType.xhtml, null, leaf);
168            }
169        } else {
170            for (String bundle : bundles) {
171                applyBundle(ctx, parent, wrm, bundle, rtype, targetValue, leaf);
172            }
173        }
174    }
175
176    protected void applyBundle(FaceletContext ctx, UIComponent parent, WebResourceManager wrm, String bundle,
177            ResourceType type, String targetValue, FaceletHandler nextHandler) throws IOException {
178        List<Resource> rs = wrm.getResources(new ResourceContextImpl(), bundle, type.name());
179        if (rs != null && !rs.isEmpty()) {
180            switch (type) {
181            case jsfjs:
182                for (Resource r : rs) {
183                    ComponentConfig config = getJSFResourceComponentConfig(r, "javax.faces.resource.Script",
184                            targetValue, nextHandler);
185                    new ScriptResourceHandler(config).apply(ctx, parent);
186                }
187                break;
188            case jsfcss:
189                for (Resource resource : rs) {
190                    ComponentConfig config = getJSFResourceComponentConfig(resource, "javax.faces.resource.Stylesheet",
191                            targetValue, nextHandler);
192                    new StylesheetResourceHandler(config).apply(ctx, parent);
193                }
194                break;
195            case xhtmlfirst:
196                includeXHTML(ctx, parent, rs, nextHandler);
197                break;
198            case xhtml:
199                includeXHTML(ctx, parent, rs, nextHandler);
200                break;
201            case js:
202                includeResourceBundle(ctx, parent, bundle, type, targetValue, nextHandler);
203                break;
204            case css:
205                includeResourceBundle(ctx, parent, bundle, type, targetValue, nextHandler);
206                break;
207            case html:
208                includeResourceBundle(ctx, parent, bundle, type, targetValue, nextHandler);
209                break;
210            default:
211                break;
212            }
213        }
214    }
215
216    protected ResourceType resolveType(String type) {
217        if (StringUtils.isBlank(type)) {
218            return ResourceType.any;
219        }
220        ResourceType parsed = ResourceType.parse(type);
221        if (parsed != null) {
222            List<ResourceType> handled = Arrays.asList(handledTypesArray);
223            if (handled.contains(parsed)) {
224                return parsed;
225            }
226        }
227        return null;
228    }
229
230    protected TagAttributeImpl getTagAttribute(String name, String value) {
231        return new TagAttributeImpl(tag.getLocation(), "", name, name, value);
232    }
233
234    protected ComponentConfig getJSFResourceComponentConfig(Resource resource, String rendererType, String target,
235            FaceletHandler nextHandler) {
236        String componentType = UIOutput.COMPONENT_TYPE;
237        String uri = resource.getURI();
238        String resourceName;
239        String resourceLib;
240        int i = uri != null ? uri.indexOf(":") : -1;
241        if (i > 0) {
242            resourceLib = uri.substring(0, i);
243            resourceName = uri.substring(i + 1);
244        } else {
245            resourceLib = null;
246            resourceName = uri;
247        }
248        List<TagAttribute> attrs = new ArrayList<TagAttribute>();
249        attrs.add(getTagAttribute("name", resourceName));
250        if (!StringUtils.isBlank(resourceLib)) {
251            attrs.add(getTagAttribute("library", resourceLib));
252        }
253        if (!StringUtils.isBlank(target)) {
254            attrs.add(getTagAttribute("target", target));
255        }
256        TagAttributesImpl attributes = new TagAttributesImpl(attrs.toArray(new TagAttribute[] {}));
257        ComponentConfig cconfig = TagConfigFactory.createComponentConfig(config, tagId, attributes, nextHandler,
258                componentType, rendererType);
259        return cconfig;
260    }
261
262    protected void includeResourceBundle(FaceletContext ctx, UIComponent parent, String name, ResourceType type,
263            String target, FaceletHandler nextHandler) throws IOException {
264        String componentType = UIOutput.COMPONENT_TYPE;
265        List<TagAttribute> attrs = new ArrayList<TagAttribute>();
266        attrs.add(getTagAttribute("name", name));
267        attrs.add(getTagAttribute("type", type.name()));
268        if (!StringUtils.isBlank(target)) {
269            attrs.add(getTagAttribute("target", target));
270        }
271        TagAttributesImpl attributes = new TagAttributesImpl(attrs.toArray(new TagAttribute[] {}));
272        ComponentConfig cconfig = TagConfigFactory.createComponentConfig(config, tagId, attributes, nextHandler,
273                componentType, ResourceBundleRenderer.RENDERER_TYPE);
274        new ComponentHandler(cconfig).apply(ctx, parent);
275    }
276
277    protected void includeXHTML(FaceletContext ctx, UIComponent parent, List<Resource> rs, FaceletHandler leaf)
278            throws IOException {
279        if (rs != null && !rs.isEmpty()) {
280            for (Resource r : rs) {
281                String uri = r.getURI();
282                if (StringUtils.isBlank(uri)) {
283                    log.error(String.format("Invalid resource '%s': no uri defined at %s", r.getName(),
284                            tag.getLocation()));
285                    continue;
286                }
287                TagAttributeImpl srcAttr = getTagAttribute("src", uri);
288                TagAttributesImpl attributes = new TagAttributesImpl(new TagAttribute[] { srcAttr });
289                TagConfig xconfig = TagConfigFactory.createTagConfig(config, tagId, attributes, leaf);
290                new IncludeHandler(xconfig).apply(ctx, parent);
291            }
292        }
293    }
294
295}