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