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.lang.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[] vars;
079
080    protected final ResourceType[] handledTypesArray = { ResourceType.css, ResourceType.js, ResourceType.jsfcss,
081            ResourceType.jsfjs, ResourceType.html, ResourceType.xhtml, ResourceType.xhtmlfirst };
082
083    public PageResourceHandler(TagConfig config) {
084        super(config);
085        this.config = config;
086        name = getAttribute("name");
087        type = getAttribute("type");
088        flavor = getAttribute("flavor");
089        target = getAttribute("target");
090        vars = tag.getAttributes().getAll();
091    }
092
093    @Override
094    @SuppressWarnings("rawtypes")
095    protected MetaRuleset createMetaRuleset(Class type) {
096        return null;
097    }
098
099    @Override
100    public void apply(FaceletContext ctx, UIComponent parent) throws IOException {
101        if (name == null) {
102            return;
103        }
104        String pageName = name.getValue(ctx);
105        ThemeStylingService tss = Framework.getService(ThemeStylingService.class);
106        PageDescriptor page = tss.getPage(pageName);
107        if (page == null) {
108            // NO-OP
109            return;
110        }
111        String typeValue = null;
112        if (type != null) {
113            typeValue = type.getValue(ctx);
114        }
115        ResourceType rtype = resolveType(typeValue);
116        if (rtype == null) {
117            log.error("Unsupported type '" + typeValue + "' on tag nxr:resourceBundle at " + tag.getLocation());
118            return;
119        }
120
121        String flavorValue = null;
122        if (flavor != null) {
123            flavorValue = flavor.getValue(ctx);
124        }
125
126        String targetValue = null;
127        if (target != null) {
128            targetValue = target.getValue(ctx);
129        }
130
131        WebResourceManager wrm = Framework.getService(WebResourceManager.class);
132        LeafFaceletHandler leaf = new LeafFaceletHandler();
133        if (rtype == ResourceType.any) {
134            String cssTarget = targetValue;
135            String jsTarget = targetValue;
136            String htmlTarget = targetValue;
137            if (vars != null) {
138                for (TagAttribute var : vars) {
139                    if ("target_css".equalsIgnoreCase(var.getLocalName())) {
140                        String val = resolveAttribute(ctx, var);
141                        if (val != null) {
142                            cssTarget = val;
143                        }
144                    } else if ("target_js".equalsIgnoreCase(var.getLocalName())) {
145                        String val = resolveAttribute(ctx, var);
146                        if (val != null) {
147                            jsTarget = val;
148                        }
149                    } else if ("target_html".equalsIgnoreCase(var.getLocalName())) {
150                        String val = resolveAttribute(ctx, var);
151                        if (val != null) {
152                            htmlTarget = val;
153                        }
154                    }
155                }
156            }
157            // first include handlers that match JSF resources
158            applyPage(ctx, parent, wrm, page, ResourceType.jsfcss, flavorValue, cssTarget, leaf);
159            applyPage(ctx, parent, wrm, page, ResourceType.jsfjs, flavorValue, jsTarget, leaf);
160            // then include xhtmlfirst templates
161            applyPage(ctx, parent, wrm, page, ResourceType.xhtmlfirst, flavorValue, null, leaf);
162            // then let other resources (css, js, html) be processed by the component at render time
163            applyPage(ctx, parent, wrm, page, ResourceType.css, flavorValue, cssTarget, nextHandler);
164            applyPage(ctx, parent, wrm, page, ResourceType.js, flavorValue, jsTarget, nextHandler);
165            applyPage(ctx, parent, wrm, page, ResourceType.html, flavorValue, htmlTarget, nextHandler);
166            // then include xhtml templates
167            applyPage(ctx, parent, wrm, page, ResourceType.xhtml, flavorValue, null, leaf);
168        } else {
169            applyPage(ctx, parent, wrm, page, rtype, flavorValue, targetValue, leaf);
170        }
171    }
172
173    protected void applyPage(FaceletContext ctx, UIComponent parent, WebResourceManager wrm, PageDescriptor page,
174            ResourceType type, String flavor, String targetValue, FaceletHandler nextHandler) throws IOException {
175        switch (type) {
176        case jsfjs:
177            for (Resource r : retrieveResources(wrm, page, type)) {
178                String rtarget = r.getTarget();
179                ComponentConfig config = getJSFResourceComponentConfig(r, "javax.faces.resource.Script",
180                        rtarget == null ? targetValue : rtarget, nextHandler);
181                new ScriptResourceHandler(config).apply(ctx, parent);
182            }
183            break;
184        case jsfcss:
185            for (Resource r : retrieveResources(wrm, page, type)) {
186                String rtarget = r.getTarget();
187                ComponentConfig config = getJSFResourceComponentConfig(r, "javax.faces.resource.Stylesheet",
188                        rtarget == null ? targetValue : rtarget, nextHandler);
189                new StylesheetResourceHandler(config).apply(ctx, parent);
190            }
191            break;
192        case xhtmlfirst:
193            includeXHTML(ctx, parent, retrieveResources(wrm, page, type), nextHandler);
194            break;
195        case xhtml:
196            includeXHTML(ctx, parent, retrieveResources(wrm, page, type), nextHandler);
197            break;
198        case js:
199            includePageResource(ctx, parent, page.getName(), type, flavor, targetValue, nextHandler);
200            break;
201        case css:
202            includePageResource(ctx, parent, page.getName(), type, flavor, targetValue, nextHandler);
203            break;
204        case html:
205            for (String bundle : page.getResourceBundles()) {
206                includeResourceBundle(ctx, parent, bundle, type, flavor, targetValue, nextHandler);
207            }
208            break;
209        default:
210            break;
211        }
212
213    }
214
215    // helper methods
216
217    protected List<Resource> retrieveResources(WebResourceManager wrm, String bundle, ResourceType type) {
218        return wrm.getResources(new ResourceContextImpl(), bundle, type.name());
219    }
220
221    protected List<Resource> retrieveResources(WebResourceManager wrm, PageDescriptor page, ResourceType type) {
222        List<Resource> res = new ArrayList<Resource>();
223        List<String> bundles = page.getResourceBundles();
224        for (String bundle : bundles) {
225            res.addAll(retrieveResources(wrm, bundle, type));
226        }
227        return res;
228    }
229
230    protected String resolveAttribute(FaceletContext ctx, TagAttribute var) {
231        String val = var.getValue(ctx);
232        if (!StringUtils.isBlank(val)) {
233            return val;
234        }
235        return null;
236    }
237
238    protected ResourceType resolveType(String type) {
239        if (StringUtils.isBlank(type)) {
240            return ResourceType.any;
241        }
242        ResourceType parsed = ResourceType.parse(type);
243        if (parsed != null) {
244            List<ResourceType> handled = Arrays.asList(handledTypesArray);
245            if (handled.contains(parsed)) {
246                return parsed;
247            }
248        }
249        return null;
250    }
251
252    protected TagAttributeImpl getTagAttribute(String name, String value) {
253        return new TagAttributeImpl(tag.getLocation(), "", name, name, value);
254    }
255
256    protected ComponentConfig getJSFResourceComponentConfig(Resource resource, String rendererType, String target,
257            FaceletHandler nextHandler) {
258        String componentType = UIOutput.COMPONENT_TYPE;
259        String uri = resource.getURI();
260        String resourceName;
261        String resourceLib;
262        int i = uri != null ? uri.indexOf(":") : -1;
263        if (i > 0) {
264            resourceLib = uri.substring(0, i);
265            resourceName = uri.substring(i + 1);
266        } else {
267            resourceLib = null;
268            resourceName = uri;
269        }
270        List<TagAttribute> attrs = new ArrayList<TagAttribute>();
271        attrs.add(getTagAttribute("name", resourceName));
272        if (!StringUtils.isBlank(resourceLib)) {
273            attrs.add(getTagAttribute("library", resourceLib));
274        }
275        if (!StringUtils.isBlank(target)) {
276            attrs.add(getTagAttribute("target", target));
277        }
278        TagAttributesImpl attributes = new TagAttributesImpl(attrs.toArray(new TagAttribute[] {}));
279        ComponentConfig cconfig = TagConfigFactory.createComponentConfig(config, tagId, attributes, nextHandler,
280                componentType, rendererType);
281        return cconfig;
282    }
283
284    protected void includeXHTML(FaceletContext ctx, UIComponent parent, List<Resource> rs, FaceletHandler leaf)
285            throws IOException {
286        if (rs != null && !rs.isEmpty()) {
287            for (Resource r : rs) {
288                String uri = r.getURI();
289                if (StringUtils.isBlank(uri)) {
290                    log.error("Invalid resource '" + r.getName() + "': no uri defined at " + 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}