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