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}