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