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