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 DocumentView docView = null; 248 DocumentViewCodecManager docViewService = getDocumentViewCodecService(); 249 if (docViewService != null) { 250 docView = docViewService.getDocumentViewFromUrl(codecName, url, desc.getNeedBaseURL(), 251 BaseURL.getLocalBaseURL(request)); 252 } 253 if (docView != null) { 254 // set pattern name 255 docView.setPatternName(patternName); 256 // set other parameters as set in the url pattern if docView does 257 // not hold them already 258 Map<String, String> docViewParameters = docView.getParameters(); 259 Map<String, String> requestParameters = URIUtils.getRequestParameters(queryString); 260 if (requestParameters != null) { 261 ValueBindingDescriptor[] bindings = desc.getValueBindings(); 262 for (ValueBindingDescriptor binding : bindings) { 263 String paramName = binding.getName(); 264 if (!docViewParameters.containsKey(paramName)) { 265 Object paramValue = requestParameters.get(paramName); 266 if (paramValue == null || paramValue instanceof String) { 267 docView.addParameter(paramName, (String) paramValue); 268 } 269 } 270 } 271 } 272 } 273 274 return docView; 275 } 276 277 protected URLPatternDescriptor getURLPatternDescriptor(DocumentView docView) { 278 URLPatternDescriptor res = null; 279 if (docView != null) { 280 String patternName = docView.getPatternName(); 281 try { 282 res = getURLPatternDescriptor(patternName); 283 } catch (IllegalArgumentException e) { 284 } 285 } 286 // if (res == null && log.isDebugEnabled()) { 287 // log.debug("Could not get url pattern for document view"); 288 // } 289 return res; 290 } 291 292 @Override 293 public String getUrlFromDocumentView(DocumentView docView, String baseUrl) { 294 String url = null; 295 String patternName = docView.getPatternName(); 296 if (patternName != null) { 297 // try with original document view pattern 298 URLPatternDescriptor desc = getURLPatternDescriptor(patternName); 299 if (desc != null) { 300 // return corresponding url 301 url = getUrlFromDocumentView(desc.getName(), docView, baseUrl); 302 } 303 } 304 if (url == null) { 305 // take first matching pattern 306 List<URLPatternDescriptor> descs = getURLPatternDescriptors(); 307 for (URLPatternDescriptor desc : descs) { 308 url = getUrlFromDocumentView(desc.getName(), docView, baseUrl); 309 if (url != null) { 310 break; 311 } 312 } 313 } 314 // if (url == null && log.isDebugEnabled()) { 315 // log.debug("Could not get url from document view"); 316 // } 317 return url; 318 } 319 320 /** 321 * Returns patterns sorted according to: 322 * <ul> 323 * <li>First patterns holding the given view id</li> 324 * <li>The default pattern if it does not hold this view id</li> 325 * <li>Other patterns not holding this view id</li> 326 * </ul> 327 * 328 * @since 5.4.2 329 * @param viewId 330 * @deprecated since 5.5 331 */ 332 @Deprecated 333 protected List<URLPatternDescriptor> getSortedURLPatternDescriptorsFor(String viewId) { 334 List<URLPatternDescriptor> sortedDescriptors = new ArrayList<URLPatternDescriptor>(); 335 List<URLPatternDescriptor> nonMatchingViewIdDescriptors = new ArrayList<URLPatternDescriptor>(); 336 337 List<URLPatternDescriptor> descriptors = getURLPatternDescriptors(); 338 for (URLPatternDescriptor descriptor : descriptors) { 339 List<String> handledViewIds = descriptor.getViewIds(); 340 if (handledViewIds != null && handledViewIds.contains(viewId)) { 341 sortedDescriptors.add(descriptor); 342 } else { 343 nonMatchingViewIdDescriptors.add(descriptor); 344 } 345 } 346 sortedDescriptors.addAll(nonMatchingViewIdDescriptors); 347 return sortedDescriptors; 348 } 349 350 @Override 351 public String getUrlFromDocumentView(String patternName, DocumentView docView, String baseUrl) { 352 if (DOWNLOADFILE_PATTERN.equals(patternName)) { 353 // this used to be a codec but now delegates to DownloadService 354 DownloadService downloadService = Framework.getService(DownloadService.class); 355 DocumentLocation docLoc = docView.getDocumentLocation(); 356 String repositoryName = docLoc.getServerName(); 357 String docId = docLoc.getDocRef().toString(); 358 String xpath = docView.getParameter(DocumentFileCodec.FILE_PROPERTY_PATH_KEY); 359 String filename = docView.getParameter(DocumentFileCodec.FILENAME_KEY); 360 String url = downloadService.getDownloadUrl(repositoryName, docId, xpath, filename); 361 if (!StringUtils.isBlank(baseUrl)) { 362 if (!baseUrl.endsWith("/")) { 363 baseUrl += "/"; 364 } 365 url = baseUrl + url; 366 } 367 return url; 368 } 369 DocumentViewCodecManager docViewService = getDocumentViewCodecService(); 370 URLPatternDescriptor desc = getURLPatternDescriptor(patternName); 371 String codecName = desc.getDocumentViewCodecName(); 372 return docViewService.getUrlFromDocumentView(codecName, docView, desc.getNeedBaseURL(), baseUrl); 373 } 374 375 @Override 376 public void applyRequestParameters(FacesContext facesContext) { 377 // try to set document view 378 ExpressionFactory ef = facesContext.getApplication().getExpressionFactory(); 379 ELContext context = facesContext.getELContext(); 380 381 HttpServletRequest httpRequest = (HttpServletRequest) facesContext.getExternalContext().getRequest(); 382 383 URLPatternDescriptor pattern = getURLPatternDescriptor(httpRequest); 384 if (pattern == null) { 385 return; 386 } 387 388 DocumentView docView = getDocumentViewFromRequest(pattern.getName(), httpRequest); 389 // pattern applies => document view will not be null 390 if (docView != null) { 391 String documentViewBinding = pattern.getDocumentViewBinding(); 392 if (documentViewBinding != null && !"".equals(documentViewBinding)) { 393 // try to set it from custom mapping 394 ValueExpression ve = ef.createValueExpression(context, pattern.getDocumentViewBinding(), Object.class); 395 ve.setValue(context, docView); 396 } 397 } 398 399 Map<String, String> docViewParameters = null; 400 if (docView != null) { 401 docViewParameters = docView.getParameters(); 402 } 403 ValueBindingDescriptor[] bindings = pattern.getValueBindings(); 404 if (bindings != null && httpRequest.getAttribute(URLPolicyService.DISABLE_ACTION_BINDING_KEY) == null) { 405 for (ValueBindingDescriptor binding : bindings) { 406 if (!binding.getCallSetter()) { 407 continue; 408 } 409 String paramName = binding.getName(); 410 // try doc view parameters 411 Object value = null; 412 if (docViewParameters != null && docViewParameters.containsKey(paramName)) { 413 value = docView.getParameter(paramName); 414 } else { 415 // try request attributes 416 value = httpRequest.getAttribute(paramName); 417 } 418 String expr = binding.getExpression(); 419 if (ComponentTagUtils.isValueReference(expr)) { 420 ValueExpression ve = ef.createValueExpression(context, expr, Object.class); 421 try { 422 ve.setValue(context, value); 423 } catch (ELException e) { 424 log.error("Could not apply request parameter '" + value + "' to expression '" + expr + "'", e); 425 } 426 } 427 } 428 } 429 } 430 431 @Override 432 public void appendParametersToRequest(FacesContext facesContext) { 433 appendParametersToRequest(facesContext, null); 434 } 435 436 public void appendParametersToRequest(FacesContext facesContext, String pattern) { 437 // try to get doc view from custom mapping 438 DocumentView docView = null; 439 ExpressionFactory ef = facesContext.getApplication().getExpressionFactory(); 440 ELContext context = facesContext.getELContext(); 441 HttpServletRequest httpRequest = (HttpServletRequest) facesContext.getExternalContext().getRequest(); 442 443 // get existing document view from given pattern, else create it 444 URLPatternDescriptor patternDesc = null; 445 if (pattern != null && !"".equals(pattern)) { 446 patternDesc = getURLPatternDescriptor(pattern); 447 } else { 448 // iterate over pattern descriptors, and take the first one that 449 // applies, or use the default one 450 List<URLPatternDescriptor> descs = getURLPatternDescriptors(); 451 boolean applies = false; 452 for (URLPatternDescriptor desc : descs) { 453 String documentViewAppliesExpr = desc.getDocumentViewBindingApplies(); 454 if (!StringUtils.isBlank(documentViewAppliesExpr)) { 455 // TODO: maybe put view id to the request to help writing 456 // the EL expression 457 ValueExpression ve = ef.createValueExpression(context, documentViewAppliesExpr, Object.class); 458 try { 459 Object res = ve.getValue(context); 460 if (Boolean.TRUE.equals(res)) { 461 applies = true; 462 } 463 } catch (ELException e) { 464 if (log.isDebugEnabled()) { 465 log.debug(String.format("Error executing expression '%s' for " + "url pattern '%s': %s", 466 documentViewAppliesExpr, desc.getName(), e.getMessage())); 467 } 468 } 469 } 470 if (applies) { 471 patternDesc = desc; 472 break; 473 } 474 } 475 if (patternDesc == null) { 476 // default on the default pattern desc 477 patternDesc = getDefaultPatternDescriptor(); 478 } 479 } 480 if (patternDesc != null) { 481 // resolved doc view values thanks to bindings 482 Object docViewValue = null; 483 String documentViewBinding = patternDesc.getDocumentViewBinding(); 484 if (!StringUtils.isBlank(documentViewBinding)) { 485 ValueExpression ve = ef.createValueExpression(context, documentViewBinding, Object.class); 486 docViewValue = ve.getValue(context); 487 } 488 if (docViewValue == null) { 489 documentViewBinding = patternDesc.getNewDocumentViewBinding(); 490 if (!StringUtils.isBlank(documentViewBinding)) { 491 ValueExpression ve = ef.createValueExpression(context, documentViewBinding, Object.class); 492 docViewValue = ve.getValue(context); 493 } 494 } 495 if (docViewValue instanceof DocumentView) { 496 docView = (DocumentView) docViewValue; 497 // set pattern name in case it was just created 498 docView.setPatternName(patternDesc.getName()); 499 ValueBindingDescriptor[] bindings = patternDesc.getValueBindings(); 500 if (bindings != null) { 501 for (ValueBindingDescriptor binding : bindings) { 502 if (!binding.getCallGetter()) { 503 continue; 504 } 505 String paramName = binding.getName(); 506 String expr = binding.getExpression(); 507 try { 508 Object value; 509 if (ComponentTagUtils.isValueReference(expr)) { 510 ValueExpression ve = ef.createValueExpression(context, expr, Object.class); 511 value = ve.getValue(context); 512 } else { 513 value = expr; 514 } 515 if (docView != null) { 516 // do not set attributes on the request as 517 // document view will be put in the request 518 // anyway 519 docView.addParameter(paramName, (String) value); 520 } else { 521 httpRequest.setAttribute(paramName, value); 522 } 523 } catch (ELException e) { 524 log.error(String.format("Could not get parameter %s from expression %s", paramName, expr), 525 e); 526 } 527 } 528 } 529 } 530 } 531 532 // save document view to the request 533 setDocumentViewInRequest(httpRequest, docView); 534 } 535 536 @Override 537 public String navigate(FacesContext facesContext) { 538 HttpServletRequest httpRequest = (HttpServletRequest) facesContext.getExternalContext().getRequest(); 539 540 URLPatternDescriptor pattern = getURLPatternDescriptor(httpRequest); 541 if (pattern == null) { 542 return null; 543 } 544 545 DocumentView docView = getDocumentViewFromRequest(pattern.getName(), httpRequest); 546 ExpressionFactory ef = facesContext.getApplication().getExpressionFactory(); 547 ELContext context = facesContext.getELContext(); 548 String actionBinding = pattern.getActionBinding(); 549 550 if (actionBinding != null && !"".equals(actionBinding) 551 && httpRequest.getAttribute(URLPolicyService.DISABLE_ACTION_BINDING_KEY) == null) { 552 MethodExpression action = ef.createMethodExpression(context, actionBinding, String.class, 553 new Class[] { DocumentView.class }); 554 return (String) action.invoke(context, new Object[] { docView }); 555 } 556 return null; 557 } 558 559 // registries management 560 561 @Override 562 public void addPatternDescriptor(URLPatternDescriptor pattern) { 563 String name = pattern.getName(); 564 if (descriptors.containsKey(name)) { 565 // no merging right now 566 descriptors.remove(name); 567 } 568 descriptors.put(pattern.getName(), pattern); 569 log.debug("Added URLPatternDescriptor: " + name); 570 } 571 572 @Override 573 public void removePatternDescriptor(URLPatternDescriptor pattern) { 574 String name = pattern.getName(); 575 descriptors.remove(name); 576 log.debug("Removed URLPatternDescriptor: " + name); 577 } 578 579 @Override 580 public void initViewIdManager(ServletContext context, HttpServletRequest request, HttpServletResponse response) { 581 if (viewIdManager == null) { 582 viewIdManager = new StaticNavigationHandler(context, request, response); 583 } 584 } 585 586 StaticNavigationHandler getViewIdManager() { 587 if (viewIdManager == null) { 588 throw new RuntimeException("View id manager is not initialized: " 589 + "URLPolicyService#initViewIdManager should " + "have been called first"); 590 } 591 return viewIdManager; 592 } 593 594 @Override 595 public String getOutcomeFromViewId(String viewId, HttpServletRequest httpRequest) { 596 return getViewIdManager().getOutcomeFromViewId(viewId); 597 } 598 599 @Override 600 public String getOutcomeFromUrl(String url, HttpServletRequest request) { 601 String baseUrl = BaseURL.getBaseURL(request); 602 // parse url to get outcome from view id 603 String viewId = url; 604 String webAppName = "/" + VirtualHostHelper.getWebAppName(request); 605 if (viewId.startsWith(baseUrl)) { 606 // url is absolute 607 viewId = '/' + viewId.substring(baseUrl.length()); 608 } else if (viewId.startsWith(webAppName)) { 609 // url is relative to the web app 610 viewId = viewId.substring(webAppName.length()); 611 } 612 int index = viewId.indexOf('?'); 613 if (index != -1) { 614 viewId = viewId.substring(0, index); 615 } 616 return getOutcomeFromViewId(viewId, request); 617 } 618 619 @Override 620 public String getViewIdFromOutcome(String outcome, HttpServletRequest httpRequest) { 621 return getViewIdManager().getViewIdFromOutcome(outcome); 622 } 623 624 @Override 625 public void clear() { 626 descriptors.clear(); 627 } 628 629 @Override 630 public void flushCache() { 631 viewIdManager = null; 632 } 633 634}