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.lang3.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    @Override
321    public String getUrlFromDocumentView(String patternName, DocumentView docView, String baseUrl) {
322        if (DOWNLOADFILE_PATTERN.equals(patternName)) {
323            // this used to be a codec but now delegates to DownloadService
324            DownloadService downloadService = Framework.getService(DownloadService.class);
325            DocumentLocation docLoc = docView.getDocumentLocation();
326            String repositoryName = docLoc.getServerName();
327            String docId = docLoc.getDocRef().toString();
328            String xpath = docView.getParameter(DocumentFileCodec.FILE_PROPERTY_PATH_KEY);
329            String filename = docView.getParameter(DocumentFileCodec.FILENAME_KEY);
330            String url = downloadService.getDownloadUrl(repositoryName, docId, xpath, filename);
331            if (!StringUtils.isBlank(baseUrl)) {
332                if (!baseUrl.endsWith("/")) {
333                    baseUrl += "/";
334                }
335                url = baseUrl + url;
336            }
337            return url;
338        }
339        DocumentViewCodecManager docViewService = getDocumentViewCodecService();
340        URLPatternDescriptor desc = getURLPatternDescriptor(patternName);
341        String codecName = desc.getDocumentViewCodecName();
342        return docViewService.getUrlFromDocumentView(codecName, docView, desc.getNeedBaseURL(), baseUrl);
343    }
344
345    @Override
346    public void applyRequestParameters(FacesContext facesContext) {
347        // try to set document view
348        ExpressionFactory ef = facesContext.getApplication().getExpressionFactory();
349        ELContext context = facesContext.getELContext();
350
351        HttpServletRequest httpRequest = (HttpServletRequest) facesContext.getExternalContext().getRequest();
352
353        URLPatternDescriptor pattern = getURLPatternDescriptor(httpRequest);
354        if (pattern == null) {
355            return;
356        }
357
358        DocumentView docView = getDocumentViewFromRequest(pattern.getName(), httpRequest);
359        // pattern applies => document view will not be null
360        if (docView != null) {
361            String documentViewBinding = pattern.getDocumentViewBinding();
362            if (documentViewBinding != null && !"".equals(documentViewBinding)) {
363                // try to set it from custom mapping
364                ValueExpression ve = ef.createValueExpression(context, pattern.getDocumentViewBinding(), Object.class);
365                ve.setValue(context, docView);
366            }
367        }
368
369        Map<String, String> docViewParameters = null;
370        if (docView != null) {
371            docViewParameters = docView.getParameters();
372        }
373        ValueBindingDescriptor[] bindings = pattern.getValueBindings();
374        if (bindings != null && httpRequest.getAttribute(URLPolicyService.DISABLE_ACTION_BINDING_KEY) == null) {
375            for (ValueBindingDescriptor binding : bindings) {
376                if (!binding.getCallSetter()) {
377                    continue;
378                }
379                String paramName = binding.getName();
380                // try doc view parameters
381                Object value = null;
382                if (docViewParameters != null && docViewParameters.containsKey(paramName)) {
383                    value = docView.getParameter(paramName);
384                } else {
385                    // try request attributes
386                    value = httpRequest.getAttribute(paramName);
387                }
388                String expr = binding.getExpression();
389                if (ComponentTagUtils.isValueReference(expr)) {
390                    ValueExpression ve = ef.createValueExpression(context, expr, Object.class);
391                    try {
392                        ve.setValue(context, value);
393                    } catch (ELException e) {
394                        log.error("Could not apply request parameter '" + value + "' to expression '" + expr + "'", e);
395                    }
396                }
397            }
398        }
399    }
400
401    @Override
402    public void appendParametersToRequest(FacesContext facesContext) {
403        appendParametersToRequest(facesContext, null);
404    }
405
406    public void appendParametersToRequest(FacesContext facesContext, String pattern) {
407        // try to get doc view from custom mapping
408        DocumentView docView = null;
409        ExpressionFactory ef = facesContext.getApplication().getExpressionFactory();
410        ELContext context = facesContext.getELContext();
411        HttpServletRequest httpRequest = (HttpServletRequest) facesContext.getExternalContext().getRequest();
412
413        // get existing document view from given pattern, else create it
414        URLPatternDescriptor patternDesc = null;
415        if (pattern != null && !"".equals(pattern)) {
416            patternDesc = getURLPatternDescriptor(pattern);
417        } else {
418            // iterate over pattern descriptors, and take the first one that
419            // applies, or use the default one
420            List<URLPatternDescriptor> descs = getURLPatternDescriptors();
421            boolean applies = false;
422            for (URLPatternDescriptor desc : descs) {
423                String documentViewAppliesExpr = desc.getDocumentViewBindingApplies();
424                if (!StringUtils.isBlank(documentViewAppliesExpr)) {
425                    // TODO: maybe put view id to the request to help writing
426                    // the EL expression
427                    ValueExpression ve = ef.createValueExpression(context, documentViewAppliesExpr, Object.class);
428                    try {
429                        Object res = ve.getValue(context);
430                        if (Boolean.TRUE.equals(res)) {
431                            applies = true;
432                        }
433                    } catch (ELException e) {
434                        if (log.isDebugEnabled()) {
435                            log.debug(String.format("Error executing expression '%s' for " + "url pattern '%s': %s",
436                                    documentViewAppliesExpr, desc.getName(), e.getMessage()));
437                        }
438                    }
439                }
440                if (applies) {
441                    patternDesc = desc;
442                    break;
443                }
444            }
445            if (patternDesc == null) {
446                // default on the default pattern desc
447                patternDesc = getDefaultPatternDescriptor();
448            }
449        }
450        if (patternDesc != null) {
451            // resolved doc view values thanks to bindings
452            Object docViewValue = null;
453            String documentViewBinding = patternDesc.getDocumentViewBinding();
454            if (!StringUtils.isBlank(documentViewBinding)) {
455                ValueExpression ve = ef.createValueExpression(context, documentViewBinding, Object.class);
456                docViewValue = ve.getValue(context);
457            }
458            if (docViewValue == null) {
459                documentViewBinding = patternDesc.getNewDocumentViewBinding();
460                if (!StringUtils.isBlank(documentViewBinding)) {
461                    ValueExpression ve = ef.createValueExpression(context, documentViewBinding, Object.class);
462                    docViewValue = ve.getValue(context);
463                }
464            }
465            if (docViewValue instanceof DocumentView) {
466                docView = (DocumentView) docViewValue;
467                // set pattern name in case it was just created
468                docView.setPatternName(patternDesc.getName());
469                ValueBindingDescriptor[] bindings = patternDesc.getValueBindings();
470                if (bindings != null) {
471                    for (ValueBindingDescriptor binding : bindings) {
472                        if (!binding.getCallGetter()) {
473                            continue;
474                        }
475                        String paramName = binding.getName();
476                        String expr = binding.getExpression();
477                        try {
478                            Object value;
479                            if (ComponentTagUtils.isValueReference(expr)) {
480                                ValueExpression ve = ef.createValueExpression(context, expr, Object.class);
481                                value = ve.getValue(context);
482                            } else {
483                                value = expr;
484                            }
485                            if (docView != null) {
486                                // do not set attributes on the request as
487                                // document view will be put in the request
488                                // anyway
489                                docView.addParameter(paramName, (String) value);
490                            } else {
491                                httpRequest.setAttribute(paramName, value);
492                            }
493                        } catch (ELException e) {
494                            log.error(String.format("Could not get parameter %s from expression %s", paramName, expr),
495                                    e);
496                        }
497                    }
498                }
499            }
500        }
501
502        // save document view to the request
503        setDocumentViewInRequest(httpRequest, docView);
504    }
505
506    @Override
507    public String navigate(FacesContext facesContext) {
508        HttpServletRequest httpRequest = (HttpServletRequest) facesContext.getExternalContext().getRequest();
509
510        URLPatternDescriptor pattern = getURLPatternDescriptor(httpRequest);
511        if (pattern == null) {
512            return null;
513        }
514
515        DocumentView docView = getDocumentViewFromRequest(pattern.getName(), httpRequest);
516        ExpressionFactory ef = facesContext.getApplication().getExpressionFactory();
517        ELContext context = facesContext.getELContext();
518        String actionBinding = pattern.getActionBinding();
519
520        if (actionBinding != null && !"".equals(actionBinding)
521                && httpRequest.getAttribute(URLPolicyService.DISABLE_ACTION_BINDING_KEY) == null) {
522            MethodExpression action = ef.createMethodExpression(context, actionBinding, String.class,
523                    new Class[] { DocumentView.class });
524            return (String) action.invoke(context, new Object[] { docView });
525        }
526        return null;
527    }
528
529    // registries management
530
531    @Override
532    public void addPatternDescriptor(URLPatternDescriptor pattern) {
533        String name = pattern.getName();
534        if (descriptors.containsKey(name)) {
535            // no merging right now
536            descriptors.remove(name);
537        }
538        descriptors.put(pattern.getName(), pattern);
539        log.debug("Added URLPatternDescriptor: " + name);
540    }
541
542    @Override
543    public void removePatternDescriptor(URLPatternDescriptor pattern) {
544        String name = pattern.getName();
545        descriptors.remove(name);
546        log.debug("Removed URLPatternDescriptor: " + name);
547    }
548
549    @Override
550    public void initViewIdManager(ServletContext context, HttpServletRequest request, HttpServletResponse response) {
551        if (viewIdManager == null) {
552            viewIdManager = new StaticNavigationHandler(context, request, response);
553        }
554    }
555
556    StaticNavigationHandler getViewIdManager() {
557        if (viewIdManager == null) {
558            throw new RuntimeException("View id manager is not initialized: "
559                    + "URLPolicyService#initViewIdManager should " + "have been called first");
560        }
561        return viewIdManager;
562    }
563
564    @Override
565    public String getOutcomeFromViewId(String viewId, HttpServletRequest httpRequest) {
566        return getViewIdManager().getOutcomeFromViewId(viewId);
567    }
568
569    @Override
570    public String getOutcomeFromUrl(String url, HttpServletRequest request) {
571        String baseUrl = BaseURL.getBaseURL(request);
572        // parse url to get outcome from view id
573        String viewId = url;
574        String webAppName = "/" + VirtualHostHelper.getWebAppName(request);
575        if (viewId.startsWith(baseUrl)) {
576            // url is absolute
577            viewId = '/' + viewId.substring(baseUrl.length());
578        } else if (viewId.startsWith(webAppName)) {
579            // url is relative to the web app
580            viewId = viewId.substring(webAppName.length());
581        }
582        int index = viewId.indexOf('?');
583        if (index != -1) {
584            viewId = viewId.substring(0, index);
585        }
586        return getOutcomeFromViewId(viewId, request);
587    }
588
589    @Override
590    public String getViewIdFromOutcome(String outcome, HttpServletRequest httpRequest) {
591        return getViewIdManager().getViewIdFromOutcome(outcome);
592    }
593
594    @Override
595    public void clear() {
596        descriptors.clear();
597    }
598
599    @Override
600    public void flushCache() {
601        viewIdManager = null;
602    }
603
604}