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; 018 019import java.io.IOException; 020import java.io.UnsupportedEncodingException; 021import java.net.URLEncoder; 022import java.util.ArrayList; 023import java.util.Map; 024 025import javax.faces.application.FacesMessage; 026import javax.faces.application.ProjectStage; 027import javax.faces.application.Resource; 028import javax.faces.component.UIComponent; 029import javax.faces.component.UIParameter; 030import javax.faces.context.FacesContext; 031 032import org.apache.commons.lang.StringUtils; 033import org.nuxeo.ecm.web.resources.api.ResourceType; 034import org.nuxeo.ecm.web.resources.api.service.WebResourceManager; 035import org.nuxeo.runtime.api.Framework; 036 037import com.sun.faces.config.WebConfiguration; 038import com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.Param; 039 040/** 041 * Base class for web resources resolution, factoring out helper methods for resources retrieval. 042 * 043 * @since 7.3 044 */ 045public abstract class AbstractResourceRenderer extends ScriptStyleBaseRenderer { 046 047 /** 048 * @since 7.10 049 */ 050 public static final String BUNDLE_ENDPOINT_PATH = "/wro/api/v1/resource/bundle/"; 051 052 /** 053 * @deprecated since 7.10, use {@link #BUNDLE_ENDPOINT_PATH} instead. 054 */ 055 @Deprecated 056 public static final String ENDPOINT_PATH = BUNDLE_ENDPOINT_PATH; 057 058 /** 059 * @since 7.10 060 */ 061 public static final String PAGE_ENDPOINT_PATH = "/wro/api/v1/resource/page/"; 062 063 public static final String COMPONENTS_PATH = "/bower_components/"; 064 065 protected static final Param[] EMPTY_PARAMS = new Param[0]; 066 067 /** 068 * Resolve url either from src, looking up resource in the war, either from JSF resources, given a name (and 069 * optional library). 070 */ 071 protected String resolveUrl(FacesContext context, UIComponent component) throws IOException { 072 Map<String, Object> attributes = component.getAttributes(); 073 String src = (String) attributes.get("src"); 074 String url; 075 if (src != null) { 076 url = resolveResourceFromSource(context, component, src); 077 } else { 078 String name = (String) attributes.get("name"); 079 String library = (String) attributes.get("library"); 080 url = resolveResourceUrl(context, component, library, name); 081 } 082 return url; 083 } 084 085 protected String resolveResourceFromSource(FacesContext context, UIComponent component, String src) 086 throws UnsupportedEncodingException { 087 String value = context.getApplication().getViewHandler().getResourceURL(context, src); 088 return getUrlWithParams(context, component, value); 089 } 090 091 protected org.nuxeo.ecm.web.resources.api.Resource resolveNuxeoResource(FacesContext context, UIComponent component, 092 String resource) throws UnsupportedEncodingException { 093 WebResourceManager wrm = Framework.getService(WebResourceManager.class); 094 return wrm.getResource(resource); 095 } 096 097 protected String resolveNuxeoResourcePath(org.nuxeo.ecm.web.resources.api.Resource resource) { 098 if (resource == null) { 099 return null; 100 } 101 String name = resource.getName(); 102 if (ResourceType.css.matches(resource)) { 103 String suffixed = name; 104 if (!suffixed.endsWith(ResourceType.css.getSuffix())) { 105 suffixed += ResourceType.css.getSuffix(); 106 } 107 return BUNDLE_ENDPOINT_PATH + suffixed; 108 } else if (ResourceType.js.matches(resource)) { 109 String suffixed = name; 110 if (!suffixed.endsWith(ResourceType.js.getSuffix())) { 111 suffixed += ResourceType.js.getSuffix(); 112 } 113 return BUNDLE_ENDPOINT_PATH + suffixed; 114 } else if (ResourceType.html.matches(resource)) { 115 // assume html resources are copied to the war "components" sub-directory for now 116 return COMPONENTS_PATH + resource.getPath(); 117 } 118 // fallback on URI 119 return resource.getURI(); 120 } 121 122 protected String resolveNuxeoResourceUrl(FacesContext context, UIComponent component, String uri) 123 throws UnsupportedEncodingException { 124 String value = context.getApplication().getViewHandler().getResourceURL(context, uri); 125 return getUrlWithParams(context, component, value); 126 } 127 128 protected String resolveResourceUrl(FacesContext context, UIComponent component, String library, String name) { 129 Map<Object, Object> contextMap = context.getAttributes(); 130 131 String key = name + library; 132 133 if (null == name) { 134 return null; 135 } 136 137 // Ensure this import is not rendered more than once per request 138 if (contextMap.containsKey(key)) { 139 return null; 140 } 141 contextMap.put(key, Boolean.TRUE); 142 143 // Special case of scripts that have query strings 144 // These scripts actually use their query strings internally, not externally 145 // so we don't need the resource to know about them 146 int queryPos = name.indexOf("?"); 147 String query = null; 148 if (queryPos > -1 && name.length() > queryPos) { 149 query = name.substring(queryPos + 1); 150 name = name.substring(0, queryPos); 151 } 152 153 Resource resource = context.getApplication().getResourceHandler().createResource(name, library); 154 155 String resourceSrc = "RES_NOT_FOUND"; 156 157 WebConfiguration webConfig = WebConfiguration.getInstance(); 158 159 if (library == null && name != null && name.startsWith( 160 webConfig.getOptionValue(WebConfiguration.WebContextInitParameter.WebAppContractsDirectory))) { 161 162 if (context.isProjectStage(ProjectStage.Development)) { 163 164 String msg = "Illegal path, direct contract references are not allowed: " + name; 165 context.addMessage(component.getClientId(context), 166 new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg)); 167 } 168 resource = null; 169 } 170 171 if (resource == null) { 172 173 if (context.isProjectStage(ProjectStage.Development)) { 174 String msg = "Unable to find resource " + (library == null ? "" : library + ", ") + name; 175 context.addMessage(component.getClientId(context), 176 new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg)); 177 } 178 179 } else { 180 resourceSrc = resource.getRequestPath(); 181 if (query != null) { 182 resourceSrc = resourceSrc + ((resourceSrc.indexOf("?") > -1) ? "&" : "?") + query; 183 } 184 resourceSrc = context.getExternalContext().encodeResourceURL(resourceSrc); 185 } 186 187 return resourceSrc; 188 } 189 190 protected String getUrlWithParams(FacesContext context, UIComponent component, String src) 191 throws UnsupportedEncodingException { 192 // Write Anchor attributes 193 194 Param paramList[] = getParamList(component); 195 StringBuffer sb = new StringBuffer(); 196 sb.append(src); 197 boolean paramWritten = false; 198 for (int i = 0, len = paramList.length; i < len; i++) { 199 String pn = paramList[i].name; 200 if (pn != null && pn.length() != 0) { 201 String pv = paramList[i].value; 202 sb.append((paramWritten) ? '&' : '?'); 203 sb.append(URLEncoder.encode(pn, "UTF-8")); 204 sb.append('='); 205 if (pv != null && pv.length() != 0) { 206 sb.append(URLEncoder.encode(pv, "UTF-8")); 207 } 208 paramWritten = true; 209 } 210 } 211 212 return context.getExternalContext().encodeResourceURL(sb.toString()); 213 } 214 215 protected Param[] getParamList(UIComponent command) { 216 String flavor = (String) command.getAttributes().get("flavor"); 217 if (StringUtils.isNotBlank(flavor) || command.getChildCount() > 0) { 218 ArrayList<Param> parameterList = new ArrayList<Param>(); 219 if (StringUtils.isNotBlank(flavor)) { 220 Param param = new Param("flavor", flavor); 221 parameterList.add(param); 222 } 223 for (UIComponent kid : command.getChildren()) { 224 if (kid instanceof UIParameter) { 225 UIParameter uiParam = (UIParameter) kid; 226 if (!uiParam.isDisable()) { 227 Object value = uiParam.getValue(); 228 Param param = new Param(uiParam.getName(), (value == null ? null : value.toString())); 229 parameterList.add(param); 230 } 231 } 232 } 233 return parameterList.toArray(new Param[parameterList.size()]); 234 } else { 235 return EMPTY_PARAMS; 236 } 237 } 238 239}