001/*
002 * (C) Copyright 2006-2007 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.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 *     <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
016 *
017 * $Id: RestDocumentLink.java 25089 2007-09-18 17:41:58Z ogrisel $
018 */
019
020package org.nuxeo.ecm.platform.ui.web.component.document;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.LinkedHashMap;
025import java.util.Map;
026
027import javax.el.ELException;
028import javax.el.ValueExpression;
029import javax.faces.FacesException;
030import javax.faces.component.ContextCallback;
031import javax.faces.component.UIComponent;
032import javax.faces.component.UIParameter;
033import javax.faces.component.html.HtmlOutputLink;
034import javax.faces.context.FacesContext;
035import javax.faces.event.FacesEvent;
036
037import org.apache.commons.lang.StringUtils;
038import org.nuxeo.ecm.core.api.DocumentLocation;
039import org.nuxeo.ecm.core.api.DocumentModel;
040import org.nuxeo.ecm.core.api.DocumentRef;
041import org.nuxeo.ecm.core.api.IdRef;
042import org.nuxeo.ecm.core.api.impl.DocumentLocationImpl;
043import org.nuxeo.ecm.platform.ui.web.api.WebActions;
044import org.nuxeo.ecm.platform.ui.web.component.VariableManager;
045import org.nuxeo.ecm.platform.ui.web.tag.fn.DocumentModelFunctions;
046import org.nuxeo.ecm.platform.ui.web.util.BaseURL;
047
048import com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.Param;
049
050/**
051 * Component that gives generates a Restful link given a document.
052 *
053 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
054 */
055public class RestDocumentLink extends HtmlOutputLink {
056
057    public static final String COMPONENT_TYPE = RestDocumentLink.class.getName();
058
059    public static final String COMPONENT_FAMILY = RestDocumentLink.class.getName();
060
061    public static final String DEFAULT_VIEW_ID = "view_documents";
062
063    protected static final Param[] EMPTY_PARAMS = new Param[0];
064
065    protected DocumentModel document;
066
067    /**
068     * @since 5.7
069     */
070    protected String repositoryName;
071
072    protected DocumentRef documentIdRef;
073
074    /**
075     * @since 7.4
076     */
077    protected DocumentRef documentPathRef;
078
079    protected String view;
080
081    protected String tab;
082
083    protected String subTab;
084
085    /**
086     * @since 5.4.2
087     */
088    protected String tabs;
089
090    protected Boolean addTabInfo;
091
092    protected String pattern;
093
094    protected Boolean newConversation;
095
096    /**
097     * @since 7.3
098     */
099    protected String baseURL;
100
101    /**
102     * @since 5.7
103     */
104    protected String var;
105
106    /**
107     * @since 5.7
108     */
109    protected Boolean resolveOnly;
110
111    @Override
112    public String getFamily() {
113        return COMPONENT_FAMILY;
114    }
115
116    /**
117     * Override to build the URL thanks to other tag attributes information.
118     * <p>
119     * The document view service is queried to build it, and the tag attribute named "value" is ignored.
120     */
121    @Override
122    public Object getValue() {
123        DocumentModel doc = getDocument();
124        DocumentRef documentIdRef = getDocumentIdRef();
125        DocumentRef documentPathRef = getDocumentPathRef();
126        String repoName = getRepositoryName();
127        if (doc == null && repoName == null || (documentIdRef != null || documentPathRef != null) && repoName == null) {
128            return null;
129        }
130
131        String viewId = getView();
132
133        Map<String, String> params = new LinkedHashMap<String, String>();
134        String tabValue = getTab();
135        String subTabValue = getSubTab();
136        String tabValues = getTabs();
137        if (tabValues == null) {
138            tabValues = "";
139        }
140        if (tabValue != null && !tabValue.isEmpty()) {
141            if (!tabValues.isEmpty()) {
142                tabValues += ",";
143            }
144            tabValues += ":" + tabValue;
145            // params.put("tabId", tabValue); // BBB
146            if (subTabValue != null) {
147                tabValues += ":" + subTabValue;
148                // params.put("subTabId", subTabValue); // BBB
149            }
150        } else {
151            if (Boolean.TRUE.equals(getAddTabInfo())) {
152                // reset tab info, resetting the tab will reset the sub tab
153                if (!tabValues.isEmpty()) {
154                    tabValues += ",";
155                }
156                tabValues += ":" + WebActions.NULL_TAB_ID;
157                // params.put("tabId", WebActions.NULL_TAB_ID); // BBB
158            }
159        }
160        if (!tabValues.isEmpty()) {
161            params.put("tabIds", tabValues);
162        }
163
164        // add parameters from f:param sub tags
165        Param[] paramTags = getParamList();
166        for (Param param : paramTags) {
167            String pn = param.name;
168            if (pn != null && pn.length() != 0) {
169                String pv = param.value;
170                if (pv != null && pv.length() != 0) {
171                    params.put(pn, pv);
172                }
173            }
174        }
175
176        String pattern = getPattern();
177        String baseURL = getBaseURL();
178        if (StringUtils.isEmpty(baseURL)) {
179            baseURL = BaseURL.getBaseURL();
180        }
181
182        // new conversation variable handled by renderer
183        boolean useNewConversation = true;
184        if (doc != null) {
185            return DocumentModelFunctions.documentUrl(pattern, doc, viewId, params, useNewConversation, baseURL);
186        } else if (documentIdRef != null) {
187            DocumentLocation docLoc = new DocumentLocationImpl(repoName, documentIdRef);
188            return DocumentModelFunctions.documentUrl(pattern, docLoc, viewId, params, useNewConversation, baseURL);
189        } else if (documentPathRef != null) {
190            DocumentLocation docLoc = new DocumentLocationImpl(repoName, documentPathRef);
191            return DocumentModelFunctions.documentUrl(pattern, docLoc, viewId, params, useNewConversation, baseURL);
192        } else {
193            return DocumentModelFunctions.repositoryUrl(pattern, repoName, viewId, params, useNewConversation, baseURL);
194        }
195    }
196
197    protected Param[] getParamList() {
198        if (getChildCount() > 0) {
199            ArrayList<Param> parameterList = new ArrayList<Param>();
200            for (UIComponent kid : getChildren()) {
201                if (kid instanceof UIParameter) {
202                    UIParameter uiParam = (UIParameter) kid;
203                    Object value = uiParam.getValue();
204                    Param param = new Param(uiParam.getName(), (value == null ? null : value.toString()));
205                    parameterList.add(param);
206                }
207            }
208            return parameterList.toArray(new Param[parameterList.size()]);
209        } else {
210            return EMPTY_PARAMS;
211        }
212
213    }
214
215    // setters and getters for tag attributes
216
217    public String getPattern() {
218        if (pattern != null) {
219            return pattern;
220        }
221        ValueExpression ve = getValueExpression("pattern");
222        if (ve != null) {
223            try {
224                return (String) ve.getValue(getFacesContext().getELContext());
225            } catch (ELException e) {
226                throw new FacesException(e);
227            }
228        } else {
229            return null;
230        }
231    }
232
233    public void setPattern(String codec) {
234        pattern = codec;
235    }
236
237    public DocumentModel getDocument() {
238        if (document != null) {
239            return document;
240        }
241        ValueExpression ve = getValueExpression("document");
242        if (ve != null) {
243            try {
244                return (DocumentModel) ve.getValue(getFacesContext().getELContext());
245            } catch (ELException e) {
246                throw new FacesException(e);
247            }
248        } else {
249            return null;
250        }
251    }
252
253    public void setDocument(DocumentModel document) {
254        this.document = document;
255    }
256
257    public String getRepositoryName() {
258        if (repositoryName != null) {
259            return repositoryName;
260        }
261        ValueExpression ve = getValueExpression("repositoryName");
262        if (ve != null) {
263            try {
264                return (String) ve.getValue(getFacesContext().getELContext());
265            } catch (ELException e) {
266                throw new FacesException(e);
267            }
268        } else {
269            return null;
270        }
271    }
272
273    public void setRepositoryName(String repositoryName) {
274        this.repositoryName = repositoryName;
275    }
276
277    public DocumentRef getDocumentIdRef() {
278        if (documentIdRef != null) {
279            return documentIdRef;
280        }
281        ValueExpression ve = getValueExpression("documentId");
282        if (ve != null) {
283            try {
284                String id = (String) ve.getValue(getFacesContext().getELContext());
285                if (id != null) {
286                    return new IdRef(id);
287                } else {
288                    return null;
289                }
290            } catch (ELException e) {
291                throw new FacesException(e);
292            }
293        } else {
294            return null;
295        }
296    }
297
298    public void setDocumentIdRef(DocumentRef documentIdRef) {
299        this.documentIdRef = documentIdRef;
300    }
301
302    public DocumentRef getDocumentPathRef() {
303        if (documentPathRef != null) {
304            return documentPathRef;
305        }
306        ValueExpression ve = getValueExpression("documentPath");
307        if (ve != null) {
308            try {
309                String id = (String) ve.getValue(getFacesContext().getELContext());
310                if (id != null) {
311                    return new IdRef(id);
312                } else {
313                    return null;
314                }
315            } catch (ELException e) {
316                throw new FacesException(e);
317            }
318        } else {
319            return null;
320        }
321    }
322
323    public void setDocumentPathRef(DocumentRef documentPathRef) {
324        this.documentPathRef = documentPathRef;
325    }
326
327    /**
328     * Returns true if URL must link to a page in a new conversation.
329     * <p>
330     * Defaults to false.
331     */
332    public Boolean getNewConversation() {
333        if (newConversation != null) {
334            return newConversation;
335        }
336        ValueExpression ve = getValueExpression("newConversation");
337        if (ve != null) {
338            try {
339                return Boolean.valueOf(!Boolean.FALSE.equals(ve.getValue(getFacesContext().getELContext())));
340            } catch (ELException e) {
341                throw new FacesException(e);
342            }
343        } else {
344            // default value
345            return Boolean.FALSE;
346        }
347    }
348
349    public void setNewConversation(Boolean newConversation) {
350        this.newConversation = newConversation;
351    }
352
353    public String getSubTab() {
354        if (subTab != null) {
355            return subTab;
356        }
357        ValueExpression ve = getValueExpression("subTab");
358        if (ve != null) {
359            try {
360                return (String) ve.getValue(getFacesContext().getELContext());
361            } catch (ELException e) {
362                throw new FacesException(e);
363            }
364        } else {
365            return null;
366        }
367    }
368
369    public void setSubTab(String subTab) {
370        this.subTab = subTab;
371    }
372
373    public String getTab() {
374        if (tab != null) {
375            return tab;
376        }
377        ValueExpression ve = getValueExpression("tab");
378        if (ve != null) {
379            try {
380                return (String) ve.getValue(getFacesContext().getELContext());
381            } catch (ELException e) {
382                throw new FacesException(e);
383            }
384        } else {
385            return null;
386        }
387    }
388
389    public void setTab(String tab) {
390        this.tab = tab;
391    }
392
393    /**
394     * @since 7.3
395     */
396    public String getBaseURL() {
397        if (baseURL != null) {
398            return baseURL;
399        }
400        ValueExpression ve = getValueExpression("baseURL");
401        if (ve != null) {
402            try {
403                return (String) ve.getValue(getFacesContext().getELContext());
404            } catch (ELException e) {
405                throw new FacesException(e);
406            }
407        } else {
408            return null;
409        }
410    }
411
412    /**
413     * @since 7.3
414     */
415    public void setBaseURL(String baseURL) {
416        this.baseURL = baseURL;
417    }
418
419    public String getView() {
420        if (view != null) {
421            return view;
422        }
423        ValueExpression ve = getValueExpression("view");
424        if (ve != null) {
425            try {
426                return (String) ve.getValue(getFacesContext().getELContext());
427            } catch (ELException e) {
428                throw new FacesException(e);
429            }
430        } else {
431            return null;
432        }
433    }
434
435    public void setView(String view) {
436        this.view = view;
437    }
438
439    public Boolean getAddTabInfo() {
440        if (addTabInfo != null) {
441            return addTabInfo;
442        }
443        ValueExpression ve = getValueExpression("addTabInfo");
444        if (ve != null) {
445            try {
446                return Boolean.valueOf(!Boolean.FALSE.equals(ve.getValue(getFacesContext().getELContext())));
447            } catch (ELException e) {
448                throw new FacesException(e);
449            }
450        } else {
451            // default value
452            return Boolean.TRUE;
453        }
454    }
455
456    public void setAddTabInfo(Boolean addTabInfo) {
457        this.addTabInfo = addTabInfo;
458    }
459
460    public String getTabs() {
461        if (tabs != null) {
462            return tabs;
463        }
464        ValueExpression ve = getValueExpression("tabs");
465        if (ve != null) {
466            try {
467                return (String) ve.getValue(getFacesContext().getELContext());
468            } catch (ELException e) {
469                throw new FacesException(e);
470            }
471        } else {
472            return null;
473        }
474    }
475
476    public void setTabs(String tabs) {
477        this.tabs = tabs;
478    }
479
480    public Boolean getResolveOnly() {
481        if (resolveOnly != null) {
482            return resolveOnly;
483        }
484        ValueExpression ve = getValueExpression("resolveOnly");
485        if (ve != null) {
486            try {
487                return Boolean.valueOf(!Boolean.FALSE.equals(ve.getValue(getFacesContext().getELContext())));
488            } catch (ELException e) {
489                throw new FacesException(e);
490            }
491        } else {
492            // default value
493            return Boolean.FALSE;
494        }
495    }
496
497    public void setResolveOnly(Boolean resolveOnly) {
498        this.resolveOnly = resolveOnly;
499    }
500
501    public String getVar() {
502        if (var != null) {
503            return var;
504        }
505        ValueExpression ve = getValueExpression("var");
506        if (ve != null) {
507            try {
508                return (String) ve.getValue(getFacesContext().getELContext());
509            } catch (ELException e) {
510                throw new FacesException(e);
511            }
512        } else {
513            return null;
514        }
515    }
516
517    public void setVar(String var) {
518        this.var = var;
519    }
520
521    // "resolveOnly" attribute management: expose value instead of rendering
522    // the tag
523
524    /**
525     * Saves the current value exposed as param to the request, and put new variable value instead.
526     * <p>
527     * Returns the original value exposed to the request.
528     *
529     * @since 5.7
530     */
531    protected Object beforeRender() {
532        String var = getVar();
533        Object orig = VariableManager.saveRequestMapVarValue(var);
534        if (Boolean.TRUE.equals(getResolveOnly())) {
535            VariableManager.putVariableToRequestParam(var, getValue());
536        }
537        return orig;
538    }
539
540    /**
541     * Restored the original value exposed as param to the request, and remove current variable value.
542     *
543     * @since 5.7
544     */
545    protected void afterRender(Object origVarValue) {
546        String var = getVar();
547        VariableManager.restoreRequestMapVarValue(var, origVarValue);
548    }
549
550    /**
551     * @since 5.7
552     */
553    @Override
554    public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback)
555            throws FacesException {
556        Object varValue = beforeRender();
557        try {
558            return super.invokeOnComponent(context, clientId, callback);
559        } finally {
560            afterRender(varValue);
561        }
562    }
563
564    /**
565     * @since 5.7
566     */
567    @Override
568    public void broadcast(FacesEvent event) {
569        Object varValue = beforeRender();
570        try {
571            super.broadcast(event);
572        } finally {
573            afterRender(varValue);
574        }
575    }
576
577    /**
578     * @since 5.7
579     */
580    @Override
581    public void encodeBegin(FacesContext context) throws IOException {
582        if (!Boolean.TRUE.equals(getResolveOnly())) {
583            super.encodeBegin(context);
584        }
585    }
586
587    @Override
588    public void encodeChildren(FacesContext context) throws IOException {
589        Object varValue = beforeRender();
590        try {
591            super.encodeChildren(context);
592        } finally {
593            afterRender(varValue);
594        }
595    }
596
597    /**
598     * @since 5.7
599     */
600    @Override
601    public void encodeEnd(FacesContext context) throws IOException {
602        if (!Boolean.TRUE.equals(getResolveOnly())) {
603            super.encodeEnd(context);
604        }
605    }
606
607    // state holder
608
609    @Override
610    public Object saveState(FacesContext context) {
611        return new Object[] { super.saveState(context), document, documentIdRef, view, tab, subTab, tabs, addTabInfo,
612                pattern, newConversation, baseURL, var, resolveOnly };
613    }
614
615    @Override
616    public void restoreState(FacesContext context, Object state) {
617        Object[] values = (Object[]) state;
618        super.restoreState(context, values[0]);
619        document = (DocumentModel) values[1];
620        documentIdRef = (DocumentRef) values[2];
621        view = (String) values[3];
622        tab = (String) values[4];
623        subTab = (String) values[5];
624        tabs = (String) values[6];
625        addTabInfo = (Boolean) values[7];
626        pattern = (String) values[8];
627        newConversation = (Boolean) values[9];
628        baseURL = (String) values[10];
629        var = (String) values[11];
630        resolveOnly = (Boolean) values[12];
631    }
632
633}