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