/*
* (C) Copyright 2006-2013 Nuxeo SAS (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl-2.1.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* 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.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.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.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);
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;
}
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() {
try {
return Framework.getService(DocumentViewCodecManager.class);
} catch (Exception e) {
log.error("Could not retrieve the document view service", e);
}
return null;
}
protected URLPatternDescriptor getURLPatternDescriptor(String patternName) {
URLPatternDescriptor desc = descriptors.get(patternName);
if (desc == null) {
throw new IllegalArgumentException("Unknown pattern " + patternName);
}
return desc;
}
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;
}
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;
}
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;
}
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;
}
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();
DocumentViewCodecManager docViewService = getDocumentViewCodecService();
DocumentView 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;
}
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;
}
/**
* Returns patterns sorted according to:
* <ul>
* <li>First patterns holding the given view id</li>
* <li>The default pattern if it does not hold this view id</li>
* <li>Other patterns not holding this view id</li>
* </ul>
*
* @since 5.4.2
* @param viewId
* @deprecated since 5.5
*/
@Deprecated
protected List<URLPatternDescriptor> getSortedURLPatternDescriptorsFor(
String viewId) {
List<URLPatternDescriptor> sortedDescriptors = new ArrayList<URLPatternDescriptor>();
List<URLPatternDescriptor> nonMatchingViewIdDescriptors = new ArrayList<URLPatternDescriptor>();
List<URLPatternDescriptor> descriptors = getURLPatternDescriptors();
for (URLPatternDescriptor descriptor : descriptors) {
List<String> handledViewIds = descriptor.getViewIds();
if (handledViewIds != null && handledViewIds.contains(viewId)) {
sortedDescriptors.add(descriptor);
} else {
nonMatchingViewIdDescriptors.add(descriptor);
}
}
sortedDescriptors.addAll(nonMatchingViewIdDescriptors);
return sortedDescriptors;
}
public String getUrlFromDocumentView(String patternName,
DocumentView docView, String baseUrl) {
DocumentViewCodecManager docViewService = getDocumentViewCodecService();
URLPatternDescriptor desc = getURLPatternDescriptor(patternName);
String codecName = desc.getDocumentViewCodecName();
return docViewService.getUrlFromDocumentView(codecName, docView,
desc.getNeedBaseURL(), baseUrl);
}
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 (Exception e) {
log.error(String.format(
"Could not apply request parameter %s "
+ "to expression %s", value, expr), e);
}
}
}
}
}
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 (Exception 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 (Exception 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);
}
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
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);
}
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);
}
public void clear() {
descriptors.clear();
}
@Override
public void flushCache() {
viewIdManager = null;
}
}