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