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) ? "&amp;" : "?") + 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}