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