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 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}