001/* 002 * (C) Copyright 2006-2013 Nuxeo SAS (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 * Nuxeo - initial API and implementation 016 * 017 * $Id: URLPolicyServiceImpl.java 29556 2008-01-23 00:59:39Z jcarsique $ 018 */ 019 020package org.nuxeo.ecm.platform.ui.web.rest.services; 021 022import java.util.ArrayList; 023import java.util.LinkedHashMap; 024import java.util.List; 025import java.util.Map; 026 027import javax.el.ELContext; 028import javax.el.ELException; 029import javax.el.ExpressionFactory; 030import javax.el.MethodExpression; 031import javax.el.ValueExpression; 032import javax.faces.context.FacesContext; 033import javax.servlet.ServletContext; 034import javax.servlet.http.HttpServletRequest; 035import javax.servlet.http.HttpServletResponse; 036 037import org.apache.commons.lang.StringUtils; 038import org.apache.commons.logging.Log; 039import org.apache.commons.logging.LogFactory; 040import org.nuxeo.common.utils.URIUtils; 041import org.nuxeo.ecm.core.api.DocumentLocation; 042import org.nuxeo.ecm.core.io.download.DownloadService; 043import org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants; 044import org.nuxeo.ecm.platform.ui.web.auth.NuxeoAuthenticationFilter; 045import org.nuxeo.ecm.platform.ui.web.rest.StaticNavigationHandler; 046import org.nuxeo.ecm.platform.ui.web.rest.api.URLPolicyService; 047import org.nuxeo.ecm.platform.ui.web.rest.descriptors.URLPatternDescriptor; 048import org.nuxeo.ecm.platform.ui.web.rest.descriptors.ValueBindingDescriptor; 049import org.nuxeo.ecm.platform.ui.web.util.BaseURL; 050import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils; 051import org.nuxeo.ecm.platform.url.api.DocumentView; 052import org.nuxeo.ecm.platform.url.api.DocumentViewCodecManager; 053import org.nuxeo.ecm.platform.url.codec.DocumentFileCodec; 054import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper; 055import org.nuxeo.runtime.api.Framework; 056 057public class URLPolicyServiceImpl implements URLPolicyService { 058 059 public static final String NAME = URLPolicyServiceImpl.class.getName(); 060 061 private static final Log log = LogFactory.getLog(URLPolicyServiceImpl.class); 062 063 // this used to be a codec but now delegates to DownloadService 064 public static final String DOWNLOADFILE_PATTERN = "downloadFile"; 065 066 protected final Map<String, URLPatternDescriptor> descriptors; 067 068 protected StaticNavigationHandler viewIdManager; 069 070 public URLPolicyServiceImpl() { 071 // make sure the descriptors list order follows registration order, as 072 // order may have an impact on resolved pattern 073 descriptors = new LinkedHashMap<String, URLPatternDescriptor>(); 074 } 075 076 protected List<URLPatternDescriptor> getURLPatternDescriptors() { 077 // TODO: add cache 078 List<URLPatternDescriptor> lst = new ArrayList<URLPatternDescriptor>(); 079 for (URLPatternDescriptor desc : descriptors.values()) { 080 if (desc.getEnabled()) { 081 // add default at first 082 if (desc.getDefaultURLPolicy()) { 083 lst.add(0, desc); 084 } else { 085 lst.add(desc); 086 } 087 } 088 } 089 return lst; 090 } 091 092 protected URLPatternDescriptor getDefaultPatternDescriptor() { 093 for (URLPatternDescriptor desc : descriptors.values()) { 094 if (desc.getEnabled()) { 095 if (desc.getDefaultURLPolicy()) { 096 return desc; 097 } 098 } 099 } 100 return null; 101 } 102 103 @Override 104 public String getDefaultPatternName() { 105 URLPatternDescriptor desc = getDefaultPatternDescriptor(); 106 if (desc != null) { 107 return desc.getName(); 108 } 109 return null; 110 } 111 112 @Override 113 public boolean hasPattern(String name) { 114 URLPatternDescriptor desc = descriptors.get(name); 115 return desc != null; 116 } 117 118 protected static DocumentViewCodecManager getDocumentViewCodecService() { 119 return Framework.getService(DocumentViewCodecManager.class); 120 } 121 122 protected URLPatternDescriptor getURLPatternDescriptor(String patternName) { 123 URLPatternDescriptor desc = descriptors.get(patternName); 124 if (desc == null) { 125 throw new IllegalArgumentException("Unknown pattern " + patternName); 126 } 127 return desc; 128 } 129 130 @Override 131 public boolean isCandidateForDecoding(HttpServletRequest httpRequest) { 132 // only rewrite GET/HEAD URLs 133 String method = httpRequest.getMethod(); 134 if (!method.equals("GET") && !method.equals("HEAD")) { 135 return false; 136 } 137 138 // look for appropriate pattern and see if it needs filter 139 // preprocessing 140 URLPatternDescriptor desc = getURLPatternDescriptor(httpRequest); 141 if (desc != null) { 142 return desc.getNeedFilterPreprocessing(); 143 } 144 // return default pattern descriptor behaviour 145 URLPatternDescriptor defaultPattern = getDefaultPatternDescriptor(); 146 if (defaultPattern != null) { 147 return defaultPattern.getNeedFilterPreprocessing(); 148 } 149 return false; 150 } 151 152 @Override 153 public boolean isCandidateForEncoding(HttpServletRequest httpRequest) { 154 Boolean forceEncoding = Boolean.FALSE; 155 Object forceEncodingValue = httpRequest.getAttribute(FORCE_URL_ENCODING_REQUEST_KEY); 156 if (forceEncodingValue instanceof Boolean) { 157 forceEncoding = (Boolean) forceEncodingValue; 158 } 159 160 // only POST access need a redirect,unless with force encoding (this 161 // happens when redirect is triggered after a seam page has been 162 // processed) 163 if (!forceEncoding.booleanValue() && !httpRequest.getMethod().equals("POST")) { 164 return false; 165 } 166 167 Object skipRedirect = httpRequest.getAttribute(NXAuthConstants.DISABLE_REDIRECT_REQUEST_KEY); 168 if (skipRedirect instanceof Boolean && ((Boolean) skipRedirect).booleanValue()) { 169 return false; 170 } 171 172 // look for appropriate pattern and see if it needs redirect 173 URLPatternDescriptor desc = getURLPatternDescriptor(httpRequest); 174 if (desc != null) { 175 return desc.getNeedRedirectFilter(); 176 } 177 // return default pattern descriptor behaviour 178 URLPatternDescriptor defaultPattern = getDefaultPatternDescriptor(); 179 if (defaultPattern != null) { 180 return defaultPattern.getNeedRedirectFilter(); 181 } 182 return false; 183 } 184 185 @Override 186 public void setDocumentViewInRequest(HttpServletRequest request, DocumentView docView) { 187 request.setAttribute(NXAuthConstants.REQUESTED_URL, NuxeoAuthenticationFilter.getRequestedUrl(request)); 188 request.setAttribute(DOCUMENT_VIEW_REQUEST_KEY, docView); 189 } 190 191 protected URLPatternDescriptor getURLPatternDescriptor(HttpServletRequest request) { 192 URLPatternDescriptor res = null; 193 for (URLPatternDescriptor desc : getURLPatternDescriptors()) { 194 DocumentView docView = getDocumentViewFromRequest(desc.getName(), request); 195 if (docView != null) { 196 res = desc; 197 break; 198 } 199 } 200 // if (res == null && log.isDebugEnabled()) { 201 // log.debug("Could not get url pattern for request " 202 // + request.getRequestURL()); 203 // } 204 return res; 205 } 206 207 @Override 208 public DocumentView getDocumentViewFromRequest(HttpServletRequest request) { 209 DocumentView docView = null; 210 for (URLPatternDescriptor desc : getURLPatternDescriptors()) { 211 docView = getDocumentViewFromRequest(desc.getName(), request); 212 if (docView != null) { 213 break; 214 } 215 } 216 217 // if (docView == null && log.isDebugEnabled()) { 218 // log.debug("Could not get document view from request " 219 // + request.getRequestURL()); 220 // } 221 return docView; 222 } 223 224 @Override 225 public DocumentView getDocumentViewFromRequest(String patternName, HttpServletRequest request) { 226 Object value = request.getAttribute(DOCUMENT_VIEW_REQUEST_KEY); 227 if (value instanceof DocumentView) { 228 DocumentView requestDocView = (DocumentView) value; 229 // check if document view in request was set thanks to this pattern 230 if (patternName.equals(requestDocView.getPatternName())) { 231 return requestDocView; 232 } 233 } 234 235 // try to build it from the request 236 String url; 237 String queryString = request.getQueryString(); 238 if (queryString != null) { 239 url = new String(request.getRequestURL() + "?" + queryString); 240 } else { 241 url = new String(request.getRequestURL()); 242 } 243 URLPatternDescriptor desc = getURLPatternDescriptor(patternName); 244 String codecName = desc.getDocumentViewCodecName(); 245 DocumentViewCodecManager docViewService = getDocumentViewCodecService(); 246 DocumentView docView = docViewService.getDocumentViewFromUrl(codecName, url, desc.getNeedBaseURL(), 247 BaseURL.getLocalBaseURL(request)); 248 if (docView != null) { 249 // set pattern name 250 docView.setPatternName(patternName); 251 // set other parameters as set in the url pattern if docView does 252 // not hold them already 253 Map<String, String> docViewParameters = docView.getParameters(); 254 Map<String, String> requestParameters = URIUtils.getRequestParameters(queryString); 255 if (requestParameters != null) { 256 ValueBindingDescriptor[] bindings = desc.getValueBindings(); 257 for (ValueBindingDescriptor binding : bindings) { 258 String paramName = binding.getName(); 259 if (!docViewParameters.containsKey(paramName)) { 260 Object paramValue = requestParameters.get(paramName); 261 if (paramValue == null || paramValue instanceof String) { 262 docView.addParameter(paramName, (String) paramValue); 263 } 264 } 265 } 266 } 267 } 268 269 return docView; 270 } 271 272 protected URLPatternDescriptor getURLPatternDescriptor(DocumentView docView) { 273 URLPatternDescriptor res = null; 274 if (docView != null) { 275 String patternName = docView.getPatternName(); 276 try { 277 res = getURLPatternDescriptor(patternName); 278 } catch (IllegalArgumentException e) { 279 } 280 } 281 // if (res == null && log.isDebugEnabled()) { 282 // log.debug("Could not get url pattern for document view"); 283 // } 284 return res; 285 } 286 287 @Override 288 public String getUrlFromDocumentView(DocumentView docView, String baseUrl) { 289 String url = null; 290 String patternName = docView.getPatternName(); 291 if (patternName != null) { 292 // try with original document view pattern 293 URLPatternDescriptor desc = getURLPatternDescriptor(patternName); 294 if (desc != null) { 295 // return corresponding url 296 url = getUrlFromDocumentView(desc.getName(), docView, baseUrl); 297 } 298 } 299 if (url == null) { 300 // take first matching pattern 301 List<URLPatternDescriptor> descs = getURLPatternDescriptors(); 302 for (URLPatternDescriptor desc : descs) { 303 url = getUrlFromDocumentView(desc.getName(), docView, baseUrl); 304 if (url != null) { 305 break; 306 } 307 } 308 } 309 // if (url == null && log.isDebugEnabled()) { 310 // log.debug("Could not get url from document view"); 311 // } 312 return url; 313 } 314 315 /** 316 * Returns patterns sorted according to: 317 * <ul> 318 * <li>First patterns holding the given view id</li> 319 * <li>The default pattern if it does not hold this view id</li> 320 * <li>Other patterns not holding this view id</li> 321 * </ul> 322 * 323 * @since 5.4.2 324 * @param viewId 325 * @deprecated since 5.5 326 */ 327 @Deprecated 328 protected List<URLPatternDescriptor> getSortedURLPatternDescriptorsFor(String viewId) { 329 List<URLPatternDescriptor> sortedDescriptors = new ArrayList<URLPatternDescriptor>(); 330 List<URLPatternDescriptor> nonMatchingViewIdDescriptors = new ArrayList<URLPatternDescriptor>(); 331 332 List<URLPatternDescriptor> descriptors = getURLPatternDescriptors(); 333 for (URLPatternDescriptor descriptor : descriptors) { 334 List<String> handledViewIds = descriptor.getViewIds(); 335 if (handledViewIds != null && handledViewIds.contains(viewId)) { 336 sortedDescriptors.add(descriptor); 337 } else { 338 nonMatchingViewIdDescriptors.add(descriptor); 339 } 340 } 341 sortedDescriptors.addAll(nonMatchingViewIdDescriptors); 342 return sortedDescriptors; 343 } 344 345 @Override 346 public String getUrlFromDocumentView(String patternName, DocumentView docView, String baseUrl) { 347 if (DOWNLOADFILE_PATTERN.equals(patternName)) { 348 // this used to be a codec but now delegates to DownloadService 349 DownloadService downloadService = Framework.getService(DownloadService.class); 350 DocumentLocation docLoc = docView.getDocumentLocation(); 351 String repositoryName = docLoc.getServerName(); 352 String docId = docLoc.getDocRef().toString(); 353 String xpath = docView.getParameter(DocumentFileCodec.FILE_PROPERTY_PATH_KEY); 354 String filename = docView.getParameter(DocumentFileCodec.FILENAME_KEY); 355 String url = downloadService.getDownloadUrl(repositoryName, docId, xpath, filename); 356 if (!StringUtils.isBlank(baseUrl)) { 357 if (!baseUrl.endsWith("/")) { 358 baseUrl += "/"; 359 } 360 url = baseUrl + url; 361 } 362 return url; 363 } 364 DocumentViewCodecManager docViewService = getDocumentViewCodecService(); 365 URLPatternDescriptor desc = getURLPatternDescriptor(patternName); 366 String codecName = desc.getDocumentViewCodecName(); 367 return docViewService.getUrlFromDocumentView(codecName, docView, desc.getNeedBaseURL(), baseUrl); 368 } 369 370 @Override 371 public void applyRequestParameters(FacesContext facesContext) { 372 // try to set document view 373 ExpressionFactory ef = facesContext.getApplication().getExpressionFactory(); 374 ELContext context = facesContext.getELContext(); 375 376 HttpServletRequest httpRequest = (HttpServletRequest) facesContext.getExternalContext().getRequest(); 377 378 URLPatternDescriptor pattern = getURLPatternDescriptor(httpRequest); 379 if (pattern == null) { 380 return; 381 } 382 383 DocumentView docView = getDocumentViewFromRequest(pattern.getName(), httpRequest); 384 // pattern applies => document view will not be null 385 if (docView != null) { 386 String documentViewBinding = pattern.getDocumentViewBinding(); 387 if (documentViewBinding != null && !"".equals(documentViewBinding)) { 388 // try to set it from custom mapping 389 ValueExpression ve = ef.createValueExpression(context, pattern.getDocumentViewBinding(), Object.class); 390 ve.setValue(context, docView); 391 } 392 } 393 394 Map<String, String> docViewParameters = null; 395 if (docView != null) { 396 docViewParameters = docView.getParameters(); 397 } 398 ValueBindingDescriptor[] bindings = pattern.getValueBindings(); 399 if (bindings != null && httpRequest.getAttribute(URLPolicyService.DISABLE_ACTION_BINDING_KEY) == null) { 400 for (ValueBindingDescriptor binding : bindings) { 401 if (!binding.getCallSetter()) { 402 continue; 403 } 404 String paramName = binding.getName(); 405 // try doc view parameters 406 Object value = null; 407 if (docViewParameters != null && docViewParameters.containsKey(paramName)) { 408 value = docView.getParameter(paramName); 409 } else { 410 // try request attributes 411 value = httpRequest.getAttribute(paramName); 412 } 413 String expr = binding.getExpression(); 414 if (ComponentTagUtils.isValueReference(expr)) { 415 ValueExpression ve = ef.createValueExpression(context, expr, Object.class); 416 try { 417 ve.setValue(context, value); 418 } catch (ELException e) { 419 log.error( 420 String.format("Could not apply request parameter %s " + "to expression %s", value, expr), 421 e); 422 } 423 } 424 } 425 } 426 } 427 428 @Override 429 public void appendParametersToRequest(FacesContext facesContext) { 430 appendParametersToRequest(facesContext, null); 431 } 432 433 public void appendParametersToRequest(FacesContext facesContext, String pattern) { 434 // try to get doc view from custom mapping 435 DocumentView docView = null; 436 ExpressionFactory ef = facesContext.getApplication().getExpressionFactory(); 437 ELContext context = facesContext.getELContext(); 438 HttpServletRequest httpRequest = (HttpServletRequest) facesContext.getExternalContext().getRequest(); 439 440 // get existing document view from given pattern, else create it 441 URLPatternDescriptor patternDesc = null; 442 if (pattern != null && !"".equals(pattern)) { 443 patternDesc = getURLPatternDescriptor(pattern); 444 } else { 445 // iterate over pattern descriptors, and take the first one that 446 // applies, or use the default one 447 List<URLPatternDescriptor> descs = getURLPatternDescriptors(); 448 boolean applies = false; 449 for (URLPatternDescriptor desc : descs) { 450 String documentViewAppliesExpr = desc.getDocumentViewBindingApplies(); 451 if (!StringUtils.isBlank(documentViewAppliesExpr)) { 452 // TODO: maybe put view id to the request to help writing 453 // the EL expression 454 ValueExpression ve = ef.createValueExpression(context, documentViewAppliesExpr, Object.class); 455 try { 456 Object res = ve.getValue(context); 457 if (Boolean.TRUE.equals(res)) { 458 applies = true; 459 } 460 } catch (ELException e) { 461 if (log.isDebugEnabled()) { 462 log.debug(String.format("Error executing expression '%s' for " + "url pattern '%s': %s", 463 documentViewAppliesExpr, desc.getName(), e.getMessage())); 464 } 465 } 466 } 467 if (applies) { 468 patternDesc = desc; 469 break; 470 } 471 } 472 if (patternDesc == null) { 473 // default on the default pattern desc 474 patternDesc = getDefaultPatternDescriptor(); 475 } 476 } 477 if (patternDesc != null) { 478 // resolved doc view values thanks to bindings 479 Object docViewValue = null; 480 String documentViewBinding = patternDesc.getDocumentViewBinding(); 481 if (!StringUtils.isBlank(documentViewBinding)) { 482 ValueExpression ve = ef.createValueExpression(context, documentViewBinding, Object.class); 483 docViewValue = ve.getValue(context); 484 } 485 if (docViewValue == null) { 486 documentViewBinding = patternDesc.getNewDocumentViewBinding(); 487 if (!StringUtils.isBlank(documentViewBinding)) { 488 ValueExpression ve = ef.createValueExpression(context, documentViewBinding, Object.class); 489 docViewValue = ve.getValue(context); 490 } 491 } 492 if (docViewValue instanceof DocumentView) { 493 docView = (DocumentView) docViewValue; 494 // set pattern name in case it was just created 495 docView.setPatternName(patternDesc.getName()); 496 ValueBindingDescriptor[] bindings = patternDesc.getValueBindings(); 497 if (bindings != null) { 498 for (ValueBindingDescriptor binding : bindings) { 499 if (!binding.getCallGetter()) { 500 continue; 501 } 502 String paramName = binding.getName(); 503 String expr = binding.getExpression(); 504 try { 505 Object value; 506 if (ComponentTagUtils.isValueReference(expr)) { 507 ValueExpression ve = ef.createValueExpression(context, expr, Object.class); 508 value = ve.getValue(context); 509 } else { 510 value = expr; 511 } 512 if (docView != null) { 513 // do not set attributes on the request as 514 // document view will be put in the request 515 // anyway 516 docView.addParameter(paramName, (String) value); 517 } else { 518 httpRequest.setAttribute(paramName, value); 519 } 520 } catch (ELException e) { 521 log.error(String.format("Could not get parameter %s from expression %s", paramName, expr), 522 e); 523 } 524 } 525 } 526 } 527 } 528 529 // save document view to the request 530 setDocumentViewInRequest(httpRequest, docView); 531 } 532 533 @Override 534 public String navigate(FacesContext facesContext) { 535 HttpServletRequest httpRequest = (HttpServletRequest) facesContext.getExternalContext().getRequest(); 536 537 URLPatternDescriptor pattern = getURLPatternDescriptor(httpRequest); 538 if (pattern == null) { 539 return null; 540 } 541 542 DocumentView docView = getDocumentViewFromRequest(pattern.getName(), httpRequest); 543 ExpressionFactory ef = facesContext.getApplication().getExpressionFactory(); 544 ELContext context = facesContext.getELContext(); 545 String actionBinding = pattern.getActionBinding(); 546 547 if (actionBinding != null && !"".equals(actionBinding) 548 && httpRequest.getAttribute(URLPolicyService.DISABLE_ACTION_BINDING_KEY) == null) { 549 MethodExpression action = ef.createMethodExpression(context, actionBinding, String.class, 550 new Class[] { DocumentView.class }); 551 return (String) action.invoke(context, new Object[] { docView }); 552 } 553 return null; 554 } 555 556 // registries management 557 558 @Override 559 public void addPatternDescriptor(URLPatternDescriptor pattern) { 560 String name = pattern.getName(); 561 if (descriptors.containsKey(name)) { 562 // no merging right now 563 descriptors.remove(name); 564 } 565 descriptors.put(pattern.getName(), pattern); 566 log.debug("Added URLPatternDescriptor: " + name); 567 } 568 569 @Override 570 public void removePatternDescriptor(URLPatternDescriptor pattern) { 571 String name = pattern.getName(); 572 descriptors.remove(name); 573 log.debug("Removed URLPatternDescriptor: " + name); 574 } 575 576 @Override 577 public void initViewIdManager(ServletContext context, HttpServletRequest request, HttpServletResponse response) { 578 if (viewIdManager == null) { 579 viewIdManager = new StaticNavigationHandler(context, request, response); 580 } 581 } 582 583 StaticNavigationHandler getViewIdManager() { 584 if (viewIdManager == null) { 585 throw new RuntimeException("View id manager is not initialized: " 586 + "URLPolicyService#initViewIdManager should " + "have been called first"); 587 } 588 return viewIdManager; 589 } 590 591 @Override 592 public String getOutcomeFromViewId(String viewId, HttpServletRequest httpRequest) { 593 return getViewIdManager().getOutcomeFromViewId(viewId); 594 } 595 596 @Override 597 public String getOutcomeFromUrl(String url, HttpServletRequest request) { 598 String baseUrl = BaseURL.getBaseURL(request); 599 // parse url to get outcome from view id 600 String viewId = url; 601 String webAppName = "/" + VirtualHostHelper.getWebAppName(request); 602 if (viewId.startsWith(baseUrl)) { 603 // url is absolute 604 viewId = '/' + viewId.substring(baseUrl.length()); 605 } else if (viewId.startsWith(webAppName)) { 606 // url is relative to the web app 607 viewId = viewId.substring(webAppName.length()); 608 } 609 int index = viewId.indexOf('?'); 610 if (index != -1) { 611 viewId = viewId.substring(0, index); 612 } 613 return getOutcomeFromViewId(viewId, request); 614 } 615 616 @Override 617 public String getViewIdFromOutcome(String outcome, HttpServletRequest httpRequest) { 618 return getViewIdManager().getViewIdFromOutcome(outcome); 619 } 620 621 @Override 622 public void clear() { 623 descriptors.clear(); 624 } 625 626 @Override 627 public void flushCache() { 628 viewIdManager = null; 629 } 630 631}