001/* 002 * (C) Copyright 2006-2013 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 * 019 * $Id: URLPolicyServiceImpl.java 29556 2008-01-23 00:59:39Z jcarsique $ 020 */ 021 022package org.nuxeo.ecm.platform.ui.web.rest.services; 023 024import java.util.ArrayList; 025import java.util.LinkedHashMap; 026import java.util.List; 027import java.util.Map; 028 029import javax.el.ELContext; 030import javax.el.ELException; 031import javax.el.ExpressionFactory; 032import javax.el.MethodExpression; 033import javax.el.ValueExpression; 034import javax.faces.context.FacesContext; 035import javax.servlet.ServletContext; 036import javax.servlet.http.HttpServletRequest; 037import javax.servlet.http.HttpServletResponse; 038 039import org.apache.commons.lang.StringUtils; 040import org.apache.commons.logging.Log; 041import org.apache.commons.logging.LogFactory; 042import org.nuxeo.common.utils.URIUtils; 043import org.nuxeo.ecm.core.api.DocumentLocation; 044import org.nuxeo.ecm.core.io.download.DownloadService; 045import org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants; 046import org.nuxeo.ecm.platform.ui.web.auth.NuxeoAuthenticationFilter; 047import org.nuxeo.ecm.platform.ui.web.rest.StaticNavigationHandler; 048import org.nuxeo.ecm.platform.ui.web.rest.api.URLPolicyService; 049import org.nuxeo.ecm.platform.ui.web.rest.descriptors.URLPatternDescriptor; 050import org.nuxeo.ecm.platform.ui.web.rest.descriptors.ValueBindingDescriptor; 051import org.nuxeo.ecm.platform.ui.web.util.BaseURL; 052import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils; 053import org.nuxeo.ecm.platform.url.api.DocumentView; 054import org.nuxeo.ecm.platform.url.api.DocumentViewCodecManager; 055import org.nuxeo.ecm.platform.url.codec.DocumentFileCodec; 056import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper; 057import org.nuxeo.runtime.api.Framework; 058 059public class URLPolicyServiceImpl implements URLPolicyService { 060 061 public static final String NAME = URLPolicyServiceImpl.class.getName(); 062 063 private static final Log log = LogFactory.getLog(URLPolicyServiceImpl.class); 064 065 // this used to be a codec but now delegates to DownloadService 066 public static final String DOWNLOADFILE_PATTERN = "downloadFile"; 067 068 protected final Map<String, URLPatternDescriptor> descriptors; 069 070 protected StaticNavigationHandler viewIdManager; 071 072 public URLPolicyServiceImpl() { 073 // make sure the descriptors list order follows registration order, as 074 // order may have an impact on resolved pattern 075 descriptors = new LinkedHashMap<String, URLPatternDescriptor>(); 076 } 077 078 protected List<URLPatternDescriptor> getURLPatternDescriptors() { 079 // TODO: add cache 080 List<URLPatternDescriptor> lst = new ArrayList<URLPatternDescriptor>(); 081 for (URLPatternDescriptor desc : descriptors.values()) { 082 if (desc.getEnabled()) { 083 // add default at first 084 if (desc.getDefaultURLPolicy()) { 085 lst.add(0, desc); 086 } else { 087 lst.add(desc); 088 } 089 } 090 } 091 return lst; 092 } 093 094 protected URLPatternDescriptor getDefaultPatternDescriptor() { 095 for (URLPatternDescriptor desc : descriptors.values()) { 096 if (desc.getEnabled()) { 097 if (desc.getDefaultURLPolicy()) { 098 return desc; 099 } 100 } 101 } 102 return null; 103 } 104 105 @Override 106 public String getDefaultPatternName() { 107 URLPatternDescriptor desc = getDefaultPatternDescriptor(); 108 if (desc != null) { 109 return desc.getName(); 110 } 111 return null; 112 } 113 114 @Override 115 public boolean hasPattern(String name) { 116 URLPatternDescriptor desc = descriptors.get(name); 117 return desc != null; 118 } 119 120 protected static DocumentViewCodecManager getDocumentViewCodecService() { 121 return Framework.getService(DocumentViewCodecManager.class); 122 } 123 124 protected URLPatternDescriptor getURLPatternDescriptor(String patternName) { 125 URLPatternDescriptor desc = descriptors.get(patternName); 126 if (desc == null) { 127 throw new IllegalArgumentException("Unknown pattern " + patternName); 128 } 129 return desc; 130 } 131 132 @Override 133 public boolean isCandidateForDecoding(HttpServletRequest httpRequest) { 134 // only rewrite GET/HEAD URLs 135 String method = httpRequest.getMethod(); 136 if (!method.equals("GET") && !method.equals("HEAD")) { 137 return false; 138 } 139 140 // look for appropriate pattern and see if it needs filter 141 // preprocessing 142 URLPatternDescriptor desc = getURLPatternDescriptor(httpRequest); 143 if (desc != null) { 144 return desc.getNeedFilterPreprocessing(); 145 } 146 // return default pattern descriptor behaviour 147 URLPatternDescriptor defaultPattern = getDefaultPatternDescriptor(); 148 if (defaultPattern != null) { 149 return defaultPattern.getNeedFilterPreprocessing(); 150 } 151 return false; 152 } 153 154 @Override 155 public boolean isCandidateForEncoding(HttpServletRequest httpRequest) { 156 Boolean forceEncoding = Boolean.FALSE; 157 Object forceEncodingValue = httpRequest.getAttribute(FORCE_URL_ENCODING_REQUEST_KEY); 158 if (forceEncodingValue instanceof Boolean) { 159 forceEncoding = (Boolean) forceEncodingValue; 160 } 161 162 // only POST access need a redirect,unless with force encoding (this 163 // happens when redirect is triggered after a seam page has been 164 // processed) 165 if (!forceEncoding.booleanValue() && !httpRequest.getMethod().equals("POST")) { 166 return false; 167 } 168 169 Object skipRedirect = httpRequest.getAttribute(NXAuthConstants.DISABLE_REDIRECT_REQUEST_KEY); 170 if (skipRedirect instanceof Boolean && ((Boolean) skipRedirect).booleanValue()) { 171 return false; 172 } 173 174 // look for appropriate pattern and see if it needs redirect 175 URLPatternDescriptor desc = getURLPatternDescriptor(httpRequest); 176 if (desc != null) { 177 return desc.getNeedRedirectFilter(); 178 } 179 // return default pattern descriptor behaviour 180 URLPatternDescriptor defaultPattern = getDefaultPatternDescriptor(); 181 if (defaultPattern != null) { 182 return defaultPattern.getNeedRedirectFilter(); 183 } 184 return false; 185 } 186 187 @Override 188 public void setDocumentViewInRequest(HttpServletRequest request, DocumentView docView) { 189 request.setAttribute(NXAuthConstants.REQUESTED_URL, NuxeoAuthenticationFilter.getRequestedUrl(request)); 190 request.setAttribute(DOCUMENT_VIEW_REQUEST_KEY, docView); 191 } 192 193 protected URLPatternDescriptor getURLPatternDescriptor(HttpServletRequest request) { 194 URLPatternDescriptor res = null; 195 for (URLPatternDescriptor desc : getURLPatternDescriptors()) { 196 DocumentView docView = getDocumentViewFromRequest(desc.getName(), request); 197 if (docView != null) { 198 res = desc; 199 break; 200 } 201 } 202 // if (res == null && log.isDebugEnabled()) { 203 // log.debug("Could not get url pattern for request " 204 // + request.getRequestURL()); 205 // } 206 return res; 207 } 208 209 @Override 210 public DocumentView getDocumentViewFromRequest(HttpServletRequest request) { 211 DocumentView docView = null; 212 for (URLPatternDescriptor desc : getURLPatternDescriptors()) { 213 docView = getDocumentViewFromRequest(desc.getName(), request); 214 if (docView != null) { 215 break; 216 } 217 } 218 219 // if (docView == null && log.isDebugEnabled()) { 220 // log.debug("Could not get document view from request " 221 // + request.getRequestURL()); 222 // } 223 return docView; 224 } 225 226 @Override 227 public DocumentView getDocumentViewFromRequest(String patternName, HttpServletRequest request) { 228 Object value = request.getAttribute(DOCUMENT_VIEW_REQUEST_KEY); 229 if (value instanceof DocumentView) { 230 DocumentView requestDocView = (DocumentView) value; 231 // check if document view in request was set thanks to this pattern 232 if (patternName.equals(requestDocView.getPatternName())) { 233 return requestDocView; 234 } 235 } 236 237 // try to build it from the request 238 String url; 239 String queryString = request.getQueryString(); 240 if (queryString != null) { 241 url = new String(request.getRequestURL() + "?" + queryString); 242 } else { 243 url = new String(request.getRequestURL()); 244 } 245 URLPatternDescriptor desc = getURLPatternDescriptor(patternName); 246 String codecName = desc.getDocumentViewCodecName(); 247 DocumentViewCodecManager docViewService = getDocumentViewCodecService(); 248 DocumentView docView = docViewService.getDocumentViewFromUrl(codecName, url, desc.getNeedBaseURL(), 249 BaseURL.getLocalBaseURL(request)); 250 if (docView != null) { 251 // set pattern name 252 docView.setPatternName(patternName); 253 // set other parameters as set in the url pattern if docView does 254 // not hold them already 255 Map<String, String> docViewParameters = docView.getParameters(); 256 Map<String, String> requestParameters = URIUtils.getRequestParameters(queryString); 257 if (requestParameters != null) { 258 ValueBindingDescriptor[] bindings = desc.getValueBindings(); 259 for (ValueBindingDescriptor binding : bindings) { 260 String paramName = binding.getName(); 261 if (!docViewParameters.containsKey(paramName)) { 262 Object paramValue = requestParameters.get(paramName); 263 if (paramValue == null || paramValue instanceof String) { 264 docView.addParameter(paramName, (String) paramValue); 265 } 266 } 267 } 268 } 269 } 270 271 return docView; 272 } 273 274 protected URLPatternDescriptor getURLPatternDescriptor(DocumentView docView) { 275 URLPatternDescriptor res = null; 276 if (docView != null) { 277 String patternName = docView.getPatternName(); 278 try { 279 res = getURLPatternDescriptor(patternName); 280 } catch (IllegalArgumentException e) { 281 } 282 } 283 // if (res == null && log.isDebugEnabled()) { 284 // log.debug("Could not get url pattern for document view"); 285 // } 286 return res; 287 } 288 289 @Override 290 public String getUrlFromDocumentView(DocumentView docView, String baseUrl) { 291 String url = null; 292 String patternName = docView.getPatternName(); 293 if (patternName != null) { 294 // try with original document view pattern 295 URLPatternDescriptor desc = getURLPatternDescriptor(patternName); 296 if (desc != null) { 297 // return corresponding url 298 url = getUrlFromDocumentView(desc.getName(), docView, baseUrl); 299 } 300 } 301 if (url == null) { 302 // take first matching pattern 303 List<URLPatternDescriptor> descs = getURLPatternDescriptors(); 304 for (URLPatternDescriptor desc : descs) { 305 url = getUrlFromDocumentView(desc.getName(), docView, baseUrl); 306 if (url != null) { 307 break; 308 } 309 } 310 } 311 // if (url == null && log.isDebugEnabled()) { 312 // log.debug("Could not get url from document view"); 313 // } 314 return url; 315 } 316 317 /** 318 * Returns patterns sorted according to: 319 * <ul> 320 * <li>First patterns holding the given view id</li> 321 * <li>The default pattern if it does not hold this view id</li> 322 * <li>Other patterns not holding this view id</li> 323 * </ul> 324 * 325 * @since 5.4.2 326 * @param viewId 327 * @deprecated since 5.5 328 */ 329 @Deprecated 330 protected List<URLPatternDescriptor> getSortedURLPatternDescriptorsFor(String viewId) { 331 List<URLPatternDescriptor> sortedDescriptors = new ArrayList<URLPatternDescriptor>(); 332 List<URLPatternDescriptor> nonMatchingViewIdDescriptors = new ArrayList<URLPatternDescriptor>(); 333 334 List<URLPatternDescriptor> descriptors = getURLPatternDescriptors(); 335 for (URLPatternDescriptor descriptor : descriptors) { 336 List<String> handledViewIds = descriptor.getViewIds(); 337 if (handledViewIds != null && handledViewIds.contains(viewId)) { 338 sortedDescriptors.add(descriptor); 339 } else { 340 nonMatchingViewIdDescriptors.add(descriptor); 341 } 342 } 343 sortedDescriptors.addAll(nonMatchingViewIdDescriptors); 344 return sortedDescriptors; 345 } 346 347 @Override 348 public String getUrlFromDocumentView(String patternName, DocumentView docView, String baseUrl) { 349 if (DOWNLOADFILE_PATTERN.equals(patternName)) { 350 // this used to be a codec but now delegates to DownloadService 351 DownloadService downloadService = Framework.getService(DownloadService.class); 352 DocumentLocation docLoc = docView.getDocumentLocation(); 353 String repositoryName = docLoc.getServerName(); 354 String docId = docLoc.getDocRef().toString(); 355 String xpath = docView.getParameter(DocumentFileCodec.FILE_PROPERTY_PATH_KEY); 356 String filename = docView.getParameter(DocumentFileCodec.FILENAME_KEY); 357 String url = downloadService.getDownloadUrl(repositoryName, docId, xpath, filename); 358 if (!StringUtils.isBlank(baseUrl)) { 359 if (!baseUrl.endsWith("/")) { 360 baseUrl += "/"; 361 } 362 url = baseUrl + url; 363 } 364 return url; 365 } 366 DocumentViewCodecManager docViewService = getDocumentViewCodecService(); 367 URLPatternDescriptor desc = getURLPatternDescriptor(patternName); 368 String codecName = desc.getDocumentViewCodecName(); 369 return docViewService.getUrlFromDocumentView(codecName, docView, desc.getNeedBaseURL(), baseUrl); 370 } 371 372 @Override 373 public void applyRequestParameters(FacesContext facesContext) { 374 // try to set document view 375 ExpressionFactory ef = facesContext.getApplication().getExpressionFactory(); 376 ELContext context = facesContext.getELContext(); 377 378 HttpServletRequest httpRequest = (HttpServletRequest) facesContext.getExternalContext().getRequest(); 379 380 URLPatternDescriptor pattern = getURLPatternDescriptor(httpRequest); 381 if (pattern == null) { 382 return; 383 } 384 385 DocumentView docView = getDocumentViewFromRequest(pattern.getName(), httpRequest); 386 // pattern applies => document view will not be null 387 if (docView != null) { 388 String documentViewBinding = pattern.getDocumentViewBinding(); 389 if (documentViewBinding != null && !"".equals(documentViewBinding)) { 390 // try to set it from custom mapping 391 ValueExpression ve = ef.createValueExpression(context, pattern.getDocumentViewBinding(), Object.class); 392 ve.setValue(context, docView); 393 } 394 } 395 396 Map<String, String> docViewParameters = null; 397 if (docView != null) { 398 docViewParameters = docView.getParameters(); 399 } 400 ValueBindingDescriptor[] bindings = pattern.getValueBindings(); 401 if (bindings != null && httpRequest.getAttribute(URLPolicyService.DISABLE_ACTION_BINDING_KEY) == null) { 402 for (ValueBindingDescriptor binding : bindings) { 403 if (!binding.getCallSetter()) { 404 continue; 405 } 406 String paramName = binding.getName(); 407 // try doc view parameters 408 Object value = null; 409 if (docViewParameters != null && docViewParameters.containsKey(paramName)) { 410 value = docView.getParameter(paramName); 411 } else { 412 // try request attributes 413 value = httpRequest.getAttribute(paramName); 414 } 415 String expr = binding.getExpression(); 416 if (ComponentTagUtils.isValueReference(expr)) { 417 ValueExpression ve = ef.createValueExpression(context, expr, Object.class); 418 try { 419 ve.setValue(context, value); 420 } catch (ELException e) { 421 log.error("Could not apply request parameter '" + value + "' to expression '" + expr + "'", 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}