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}