/*
* (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Anahide Tchertchian
*/
package org.nuxeo.ecm.web.resources.jsf;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.application.ProjectStage;
import javax.faces.application.Resource;
import javax.faces.component.UIComponent;
import javax.faces.component.UIParameter;
import javax.faces.context.FacesContext;
import org.apache.commons.lang.StringUtils;
import org.nuxeo.common.utils.URIUtils;
import org.nuxeo.ecm.web.resources.api.ResourceType;
import org.nuxeo.ecm.web.resources.api.service.WebResourceManager;
import org.nuxeo.runtime.api.Framework;
import com.sun.faces.config.WebConfiguration;
import com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.Param;
/**
* Base class for web resources resolution, factoring out helper methods for resources retrieval.
*
* @since 7.3
*/
public abstract class AbstractResourceRenderer extends ScriptStyleBaseRenderer {
/**
* @since 7.10
*/
public static final String BUNDLE_ENDPOINT_PATH = "/wro/api/v1/resource/bundle/";
/**
* @deprecated since 7.10, use {@link #BUNDLE_ENDPOINT_PATH} instead.
*/
@Deprecated
public static final String ENDPOINT_PATH = BUNDLE_ENDPOINT_PATH;
/**
* @since 7.10
*/
public static final String PAGE_ENDPOINT_PATH = "/wro/api/v1/resource/page/";
public static final String COMPONENTS_PATH = "/bower_components/";
protected static final Param[] EMPTY_PARAMS = new Param[0];
/**
* Resolve url either from src, looking up resource in the war, either from JSF resources, given a name (and
* optional library).
*/
protected String resolveUrl(FacesContext context, UIComponent component) throws IOException {
Map<String, Object> attributes = component.getAttributes();
String src = (String) attributes.get("src");
String url;
if (src != null) {
url = resolveResourceFromSource(context, component, src);
} else {
String name = (String) attributes.get("name");
String library = (String) attributes.get("library");
url = resolveResourceUrl(context, component, library, name);
}
return resolveUrlWithTimestamp(component, url);
}
protected String resolveUrlWithTimestamp(UIComponent component, String url) {
boolean doIncludeTimestamp = true;
Object includeTimestamp = component.getAttributes().get("includeTimestamp");
if (includeTimestamp instanceof String) {
if (!StringUtils.isBlank((String) includeTimestamp)) {
doIncludeTimestamp = Boolean.parseBoolean((String) includeTimestamp);
}
}
if (doIncludeTimestamp) {
Long timestamp = Framework.getService(WebResourceManager.class).getLastModified();
if (timestamp != null) {
return URIUtils.addParametersToURIQuery(url, Collections.singletonMap("ts", String.valueOf(timestamp)));
}
}
return url;
}
protected String resolveResourceFromSource(FacesContext context, UIComponent component, String src)
throws UnsupportedEncodingException {
String value = context.getApplication().getViewHandler().getResourceURL(context, src);
return getUrlWithParams(context, component, value);
}
protected org.nuxeo.ecm.web.resources.api.Resource resolveNuxeoResource(FacesContext context, UIComponent component,
String resource) throws UnsupportedEncodingException {
WebResourceManager wrm = Framework.getService(WebResourceManager.class);
return wrm.getResource(resource);
}
protected String resolveNuxeoResourcePath(org.nuxeo.ecm.web.resources.api.Resource resource) {
if (resource == null) {
return null;
}
String name = resource.getName();
if (ResourceType.css.matches(resource)) {
String suffixed = name;
if (!suffixed.endsWith(ResourceType.css.getSuffix())) {
suffixed += ResourceType.css.getSuffix();
}
return BUNDLE_ENDPOINT_PATH + suffixed;
} else if (ResourceType.js.matches(resource)) {
String suffixed = name;
if (!suffixed.endsWith(ResourceType.js.getSuffix())) {
suffixed += ResourceType.js.getSuffix();
}
return BUNDLE_ENDPOINT_PATH + suffixed;
} else if (ResourceType.html.matches(resource)) {
// assume html resources are copied to the war "components" sub-directory for now
return COMPONENTS_PATH + resource.getPath();
}
// fallback on URI
return resource.getURI();
}
protected String resolveNuxeoResourceUrl(FacesContext context, UIComponent component, String uri)
throws UnsupportedEncodingException {
String value = context.getApplication().getViewHandler().getResourceURL(context, uri);
return getUrlWithParams(context, component, value);
}
protected String resolveResourceUrl(FacesContext context, UIComponent component, String library, String name) {
Map<Object, Object> contextMap = context.getAttributes();
String key = name + library;
if (null == name) {
return null;
}
// Ensure this import is not rendered more than once per request
if (contextMap.containsKey(key)) {
return null;
}
contextMap.put(key, Boolean.TRUE);
// Special case of scripts that have query strings
// These scripts actually use their query strings internally, not externally
// so we don't need the resource to know about them
int queryPos = name.indexOf("?");
String query = null;
if (queryPos > -1 && name.length() > queryPos) {
query = name.substring(queryPos + 1);
name = name.substring(0, queryPos);
}
Resource resource = context.getApplication().getResourceHandler().createResource(name, library);
String resourceSrc = "RES_NOT_FOUND";
WebConfiguration webConfig = WebConfiguration.getInstance();
if (library == null && name != null && name.startsWith(
webConfig.getOptionValue(WebConfiguration.WebContextInitParameter.WebAppContractsDirectory))) {
if (context.isProjectStage(ProjectStage.Development)) {
String msg = "Illegal path, direct contract references are not allowed: " + name;
context.addMessage(component.getClientId(context),
new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg));
}
resource = null;
}
if (resource == null) {
if (context.isProjectStage(ProjectStage.Development)) {
String msg = "Unable to find resource " + (library == null ? "" : library + ", ") + name;
context.addMessage(component.getClientId(context),
new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg));
}
} else {
resourceSrc = resource.getRequestPath();
if (query != null) {
resourceSrc = resourceSrc + ((resourceSrc.indexOf("?") > -1) ? "&" : "?") + query;
}
resourceSrc = context.getExternalContext().encodeResourceURL(resourceSrc);
}
return resourceSrc;
}
protected String getUrlWithParams(FacesContext context, UIComponent component, String src)
throws UnsupportedEncodingException {
// Write Anchor attributes
Param paramList[] = getParamList(component);
StringBuffer sb = new StringBuffer();
sb.append(src);
boolean paramWritten = false;
for (int i = 0, len = paramList.length; i < len; i++) {
String pn = paramList[i].name;
if (pn != null && pn.length() != 0) {
String pv = paramList[i].value;
sb.append((paramWritten) ? '&' : '?');
sb.append(URLEncoder.encode(pn, "UTF-8"));
sb.append('=');
if (pv != null && pv.length() != 0) {
sb.append(URLEncoder.encode(pv, "UTF-8"));
}
paramWritten = true;
}
}
return context.getExternalContext().encodeResourceURL(sb.toString());
}
protected Param[] getParamList(UIComponent command) {
String flavor = (String) command.getAttributes().get("flavor");
if (StringUtils.isNotBlank(flavor) || command.getChildCount() > 0) {
ArrayList<Param> parameterList = new ArrayList<Param>();
if (StringUtils.isNotBlank(flavor)) {
Param param = new Param("flavor", flavor);
parameterList.add(param);
}
for (UIComponent kid : command.getChildren()) {
if (kid instanceof UIParameter) {
UIParameter uiParam = (UIParameter) kid;
if (!uiParam.isDisable()) {
Object value = uiParam.getValue();
Param param = new Param(uiParam.getName(), (value == null ? null : value.toString()));
parameterList.add(param);
}
}
}
return parameterList.toArray(new Param[parameterList.size()]);
} else {
return EMPTY_PARAMS;
}
}
}