/* * (C) Copyright 2006-2013 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: * Nuxeo - initial API and implementation * * $Id: URLPolicyServiceImpl.java 29556 2008-01-23 00:59:39Z jcarsique $ */ package org.nuxeo.ecm.platform.ui.web.rest.services; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.el.ELContext; import javax.el.ELException; import javax.el.ExpressionFactory; import javax.el.MethodExpression; import javax.el.ValueExpression; import javax.faces.context.FacesContext; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.common.utils.URIUtils; import org.nuxeo.ecm.core.api.DocumentLocation; import org.nuxeo.ecm.core.io.download.DownloadService; import org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants; import org.nuxeo.ecm.platform.ui.web.auth.NuxeoAuthenticationFilter; import org.nuxeo.ecm.platform.ui.web.rest.StaticNavigationHandler; import org.nuxeo.ecm.platform.ui.web.rest.api.URLPolicyService; import org.nuxeo.ecm.platform.ui.web.rest.descriptors.URLPatternDescriptor; import org.nuxeo.ecm.platform.ui.web.rest.descriptors.ValueBindingDescriptor; import org.nuxeo.ecm.platform.ui.web.util.BaseURL; import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils; import org.nuxeo.ecm.platform.url.api.DocumentView; import org.nuxeo.ecm.platform.url.api.DocumentViewCodecManager; import org.nuxeo.ecm.platform.url.codec.DocumentFileCodec; import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper; import org.nuxeo.runtime.api.Framework; public class URLPolicyServiceImpl implements URLPolicyService { public static final String NAME = URLPolicyServiceImpl.class.getName(); private static final Log log = LogFactory.getLog(URLPolicyServiceImpl.class); // this used to be a codec but now delegates to DownloadService public static final String DOWNLOADFILE_PATTERN = "downloadFile"; protected final Map<String, URLPatternDescriptor> descriptors; protected StaticNavigationHandler viewIdManager; public URLPolicyServiceImpl() { // make sure the descriptors list order follows registration order, as // order may have an impact on resolved pattern descriptors = new LinkedHashMap<String, URLPatternDescriptor>(); } protected List<URLPatternDescriptor> getURLPatternDescriptors() { // TODO: add cache List<URLPatternDescriptor> lst = new ArrayList<URLPatternDescriptor>(); for (URLPatternDescriptor desc : descriptors.values()) { if (desc.getEnabled()) { // add default at first if (desc.getDefaultURLPolicy()) { lst.add(0, desc); } else { lst.add(desc); } } } return lst; } protected URLPatternDescriptor getDefaultPatternDescriptor() { for (URLPatternDescriptor desc : descriptors.values()) { if (desc.getEnabled()) { if (desc.getDefaultURLPolicy()) { return desc; } } } return null; } @Override public String getDefaultPatternName() { URLPatternDescriptor desc = getDefaultPatternDescriptor(); if (desc != null) { return desc.getName(); } return null; } @Override public boolean hasPattern(String name) { URLPatternDescriptor desc = descriptors.get(name); return desc != null; } protected static DocumentViewCodecManager getDocumentViewCodecService() { return Framework.getService(DocumentViewCodecManager.class); } protected URLPatternDescriptor getURLPatternDescriptor(String patternName) { URLPatternDescriptor desc = descriptors.get(patternName); if (desc == null) { throw new IllegalArgumentException("Unknown pattern " + patternName); } return desc; } @Override public boolean isCandidateForDecoding(HttpServletRequest httpRequest) { // only rewrite GET/HEAD URLs String method = httpRequest.getMethod(); if (!method.equals("GET") && !method.equals("HEAD")) { return false; } // look for appropriate pattern and see if it needs filter // preprocessing URLPatternDescriptor desc = getURLPatternDescriptor(httpRequest); if (desc != null) { return desc.getNeedFilterPreprocessing(); } // return default pattern descriptor behaviour URLPatternDescriptor defaultPattern = getDefaultPatternDescriptor(); if (defaultPattern != null) { return defaultPattern.getNeedFilterPreprocessing(); } return false; } @Override public boolean isCandidateForEncoding(HttpServletRequest httpRequest) { Boolean forceEncoding = Boolean.FALSE; Object forceEncodingValue = httpRequest.getAttribute(FORCE_URL_ENCODING_REQUEST_KEY); if (forceEncodingValue instanceof Boolean) { forceEncoding = (Boolean) forceEncodingValue; } // only POST access need a redirect,unless with force encoding (this // happens when redirect is triggered after a seam page has been // processed) if (!forceEncoding.booleanValue() && !httpRequest.getMethod().equals("POST")) { return false; } Object skipRedirect = httpRequest.getAttribute(NXAuthConstants.DISABLE_REDIRECT_REQUEST_KEY); if (skipRedirect instanceof Boolean && ((Boolean) skipRedirect).booleanValue()) { return false; } // look for appropriate pattern and see if it needs redirect URLPatternDescriptor desc = getURLPatternDescriptor(httpRequest); if (desc != null) { return desc.getNeedRedirectFilter(); } // return default pattern descriptor behaviour URLPatternDescriptor defaultPattern = getDefaultPatternDescriptor(); if (defaultPattern != null) { return defaultPattern.getNeedRedirectFilter(); } return false; } @Override public void setDocumentViewInRequest(HttpServletRequest request, DocumentView docView) { request.setAttribute(NXAuthConstants.REQUESTED_URL, NuxeoAuthenticationFilter.getRequestedUrl(request)); request.setAttribute(DOCUMENT_VIEW_REQUEST_KEY, docView); } protected URLPatternDescriptor getURLPatternDescriptor(HttpServletRequest request) { URLPatternDescriptor res = null; for (URLPatternDescriptor desc : getURLPatternDescriptors()) { DocumentView docView = getDocumentViewFromRequest(desc.getName(), request); if (docView != null) { res = desc; break; } } // if (res == null && log.isDebugEnabled()) { // log.debug("Could not get url pattern for request " // + request.getRequestURL()); // } return res; } @Override public DocumentView getDocumentViewFromRequest(HttpServletRequest request) { DocumentView docView = null; for (URLPatternDescriptor desc : getURLPatternDescriptors()) { docView = getDocumentViewFromRequest(desc.getName(), request); if (docView != null) { break; } } // if (docView == null && log.isDebugEnabled()) { // log.debug("Could not get document view from request " // + request.getRequestURL()); // } return docView; } @Override public DocumentView getDocumentViewFromRequest(String patternName, HttpServletRequest request) { Object value = request.getAttribute(DOCUMENT_VIEW_REQUEST_KEY); if (value instanceof DocumentView) { DocumentView requestDocView = (DocumentView) value; // check if document view in request was set thanks to this pattern if (patternName.equals(requestDocView.getPatternName())) { return requestDocView; } } // try to build it from the request String url; String queryString = request.getQueryString(); if (queryString != null) { url = new String(request.getRequestURL() + "?" + queryString); } else { url = new String(request.getRequestURL()); } URLPatternDescriptor desc = getURLPatternDescriptor(patternName); String codecName = desc.getDocumentViewCodecName(); DocumentView docView = null; DocumentViewCodecManager docViewService = getDocumentViewCodecService(); if (docViewService != null) { docView = docViewService.getDocumentViewFromUrl(codecName, url, desc.getNeedBaseURL(), BaseURL.getLocalBaseURL(request)); } if (docView != null) { // set pattern name docView.setPatternName(patternName); // set other parameters as set in the url pattern if docView does // not hold them already Map<String, String> docViewParameters = docView.getParameters(); Map<String, String> requestParameters = URIUtils.getRequestParameters(queryString); if (requestParameters != null) { ValueBindingDescriptor[] bindings = desc.getValueBindings(); for (ValueBindingDescriptor binding : bindings) { String paramName = binding.getName(); if (!docViewParameters.containsKey(paramName)) { Object paramValue = requestParameters.get(paramName); if (paramValue == null || paramValue instanceof String) { docView.addParameter(paramName, (String) paramValue); } } } } } return docView; } protected URLPatternDescriptor getURLPatternDescriptor(DocumentView docView) { URLPatternDescriptor res = null; if (docView != null) { String patternName = docView.getPatternName(); try { res = getURLPatternDescriptor(patternName); } catch (IllegalArgumentException e) { } } // if (res == null && log.isDebugEnabled()) { // log.debug("Could not get url pattern for document view"); // } return res; } @Override public String getUrlFromDocumentView(DocumentView docView, String baseUrl) { String url = null; String patternName = docView.getPatternName(); if (patternName != null) { // try with original document view pattern URLPatternDescriptor desc = getURLPatternDescriptor(patternName); if (desc != null) { // return corresponding url url = getUrlFromDocumentView(desc.getName(), docView, baseUrl); } } if (url == null) { // take first matching pattern List<URLPatternDescriptor> descs = getURLPatternDescriptors(); for (URLPatternDescriptor desc : descs) { url = getUrlFromDocumentView(desc.getName(), docView, baseUrl); if (url != null) { break; } } } // if (url == null && log.isDebugEnabled()) { // log.debug("Could not get url from document view"); // } return url; } @Override public String getUrlFromDocumentView(String patternName, DocumentView docView, String baseUrl) { if (DOWNLOADFILE_PATTERN.equals(patternName)) { // this used to be a codec but now delegates to DownloadService DownloadService downloadService = Framework.getService(DownloadService.class); DocumentLocation docLoc = docView.getDocumentLocation(); String repositoryName = docLoc.getServerName(); String docId = docLoc.getDocRef().toString(); String xpath = docView.getParameter(DocumentFileCodec.FILE_PROPERTY_PATH_KEY); String filename = docView.getParameter(DocumentFileCodec.FILENAME_KEY); String url = downloadService.getDownloadUrl(repositoryName, docId, xpath, filename); if (!StringUtils.isBlank(baseUrl)) { if (!baseUrl.endsWith("/")) { baseUrl += "/"; } url = baseUrl + url; } return url; } DocumentViewCodecManager docViewService = getDocumentViewCodecService(); URLPatternDescriptor desc = getURLPatternDescriptor(patternName); String codecName = desc.getDocumentViewCodecName(); return docViewService.getUrlFromDocumentView(codecName, docView, desc.getNeedBaseURL(), baseUrl); } @Override public void applyRequestParameters(FacesContext facesContext) { // try to set document view ExpressionFactory ef = facesContext.getApplication().getExpressionFactory(); ELContext context = facesContext.getELContext(); HttpServletRequest httpRequest = (HttpServletRequest) facesContext.getExternalContext().getRequest(); URLPatternDescriptor pattern = getURLPatternDescriptor(httpRequest); if (pattern == null) { return; } DocumentView docView = getDocumentViewFromRequest(pattern.getName(), httpRequest); // pattern applies => document view will not be null if (docView != null) { String documentViewBinding = pattern.getDocumentViewBinding(); if (documentViewBinding != null && !"".equals(documentViewBinding)) { // try to set it from custom mapping ValueExpression ve = ef.createValueExpression(context, pattern.getDocumentViewBinding(), Object.class); ve.setValue(context, docView); } } Map<String, String> docViewParameters = null; if (docView != null) { docViewParameters = docView.getParameters(); } ValueBindingDescriptor[] bindings = pattern.getValueBindings(); if (bindings != null && httpRequest.getAttribute(URLPolicyService.DISABLE_ACTION_BINDING_KEY) == null) { for (ValueBindingDescriptor binding : bindings) { if (!binding.getCallSetter()) { continue; } String paramName = binding.getName(); // try doc view parameters Object value = null; if (docViewParameters != null && docViewParameters.containsKey(paramName)) { value = docView.getParameter(paramName); } else { // try request attributes value = httpRequest.getAttribute(paramName); } String expr = binding.getExpression(); if (ComponentTagUtils.isValueReference(expr)) { ValueExpression ve = ef.createValueExpression(context, expr, Object.class); try { ve.setValue(context, value); } catch (ELException e) { log.error("Could not apply request parameter '" + value + "' to expression '" + expr + "'", e); } } } } } @Override public void appendParametersToRequest(FacesContext facesContext) { appendParametersToRequest(facesContext, null); } public void appendParametersToRequest(FacesContext facesContext, String pattern) { // try to get doc view from custom mapping DocumentView docView = null; ExpressionFactory ef = facesContext.getApplication().getExpressionFactory(); ELContext context = facesContext.getELContext(); HttpServletRequest httpRequest = (HttpServletRequest) facesContext.getExternalContext().getRequest(); // get existing document view from given pattern, else create it URLPatternDescriptor patternDesc = null; if (pattern != null && !"".equals(pattern)) { patternDesc = getURLPatternDescriptor(pattern); } else { // iterate over pattern descriptors, and take the first one that // applies, or use the default one List<URLPatternDescriptor> descs = getURLPatternDescriptors(); boolean applies = false; for (URLPatternDescriptor desc : descs) { String documentViewAppliesExpr = desc.getDocumentViewBindingApplies(); if (!StringUtils.isBlank(documentViewAppliesExpr)) { // TODO: maybe put view id to the request to help writing // the EL expression ValueExpression ve = ef.createValueExpression(context, documentViewAppliesExpr, Object.class); try { Object res = ve.getValue(context); if (Boolean.TRUE.equals(res)) { applies = true; } } catch (ELException e) { if (log.isDebugEnabled()) { log.debug(String.format("Error executing expression '%s' for " + "url pattern '%s': %s", documentViewAppliesExpr, desc.getName(), e.getMessage())); } } } if (applies) { patternDesc = desc; break; } } if (patternDesc == null) { // default on the default pattern desc patternDesc = getDefaultPatternDescriptor(); } } if (patternDesc != null) { // resolved doc view values thanks to bindings Object docViewValue = null; String documentViewBinding = patternDesc.getDocumentViewBinding(); if (!StringUtils.isBlank(documentViewBinding)) { ValueExpression ve = ef.createValueExpression(context, documentViewBinding, Object.class); docViewValue = ve.getValue(context); } if (docViewValue == null) { documentViewBinding = patternDesc.getNewDocumentViewBinding(); if (!StringUtils.isBlank(documentViewBinding)) { ValueExpression ve = ef.createValueExpression(context, documentViewBinding, Object.class); docViewValue = ve.getValue(context); } } if (docViewValue instanceof DocumentView) { docView = (DocumentView) docViewValue; // set pattern name in case it was just created docView.setPatternName(patternDesc.getName()); ValueBindingDescriptor[] bindings = patternDesc.getValueBindings(); if (bindings != null) { for (ValueBindingDescriptor binding : bindings) { if (!binding.getCallGetter()) { continue; } String paramName = binding.getName(); String expr = binding.getExpression(); try { Object value; if (ComponentTagUtils.isValueReference(expr)) { ValueExpression ve = ef.createValueExpression(context, expr, Object.class); value = ve.getValue(context); } else { value = expr; } if (docView != null) { // do not set attributes on the request as // document view will be put in the request // anyway docView.addParameter(paramName, (String) value); } else { httpRequest.setAttribute(paramName, value); } } catch (ELException e) { log.error(String.format("Could not get parameter %s from expression %s", paramName, expr), e); } } } } } // save document view to the request setDocumentViewInRequest(httpRequest, docView); } @Override public String navigate(FacesContext facesContext) { HttpServletRequest httpRequest = (HttpServletRequest) facesContext.getExternalContext().getRequest(); URLPatternDescriptor pattern = getURLPatternDescriptor(httpRequest); if (pattern == null) { return null; } DocumentView docView = getDocumentViewFromRequest(pattern.getName(), httpRequest); ExpressionFactory ef = facesContext.getApplication().getExpressionFactory(); ELContext context = facesContext.getELContext(); String actionBinding = pattern.getActionBinding(); if (actionBinding != null && !"".equals(actionBinding) && httpRequest.getAttribute(URLPolicyService.DISABLE_ACTION_BINDING_KEY) == null) { MethodExpression action = ef.createMethodExpression(context, actionBinding, String.class, new Class[] { DocumentView.class }); return (String) action.invoke(context, new Object[] { docView }); } return null; } // registries management @Override public void addPatternDescriptor(URLPatternDescriptor pattern) { String name = pattern.getName(); if (descriptors.containsKey(name)) { // no merging right now descriptors.remove(name); } descriptors.put(pattern.getName(), pattern); log.debug("Added URLPatternDescriptor: " + name); } @Override public void removePatternDescriptor(URLPatternDescriptor pattern) { String name = pattern.getName(); descriptors.remove(name); log.debug("Removed URLPatternDescriptor: " + name); } @Override public void initViewIdManager(ServletContext context, HttpServletRequest request, HttpServletResponse response) { if (viewIdManager == null) { viewIdManager = new StaticNavigationHandler(context, request, response); } } StaticNavigationHandler getViewIdManager() { if (viewIdManager == null) { throw new RuntimeException("View id manager is not initialized: " + "URLPolicyService#initViewIdManager should " + "have been called first"); } return viewIdManager; } @Override public String getOutcomeFromViewId(String viewId, HttpServletRequest httpRequest) { return getViewIdManager().getOutcomeFromViewId(viewId); } @Override public String getOutcomeFromUrl(String url, HttpServletRequest request) { String baseUrl = BaseURL.getBaseURL(request); // parse url to get outcome from view id String viewId = url; String webAppName = "/" + VirtualHostHelper.getWebAppName(request); if (viewId.startsWith(baseUrl)) { // url is absolute viewId = '/' + viewId.substring(baseUrl.length()); } else if (viewId.startsWith(webAppName)) { // url is relative to the web app viewId = viewId.substring(webAppName.length()); } int index = viewId.indexOf('?'); if (index != -1) { viewId = viewId.substring(0, index); } return getOutcomeFromViewId(viewId, request); } @Override public String getViewIdFromOutcome(String outcome, HttpServletRequest httpRequest) { return getViewIdManager().getViewIdFromOutcome(outcome); } @Override public void clear() { descriptors.clear(); } @Override public void flushCache() { viewIdManager = null; } }