001/*
002 * (C) Copyright 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 *     Nuxeo - initial API and implementation
018 *
019 * $Id: DocumentModelFunctions.java 30568 2008-02-25 18:52:49Z ogrisel $
020 */
021
022package org.nuxeo.ecm.platform.ui.web.tag.fn;
023
024import java.io.Serializable;
025import java.io.UnsupportedEncodingException;
026import java.net.URLEncoder;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.HashMap;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map;
033
034import javax.faces.context.FacesContext;
035import javax.servlet.http.Cookie;
036import javax.servlet.http.HttpServletRequest;
037
038import org.apache.commons.lang.StringUtils;
039import org.apache.commons.logging.Log;
040import org.apache.commons.logging.LogFactory;
041import org.jboss.seam.Component;
042import org.jboss.seam.ScopeType;
043import org.jboss.seam.core.Manager;
044import org.nuxeo.ecm.core.NXCore;
045import org.nuxeo.ecm.core.api.Blob;
046import org.nuxeo.ecm.core.api.CoreSession;
047import org.nuxeo.ecm.core.api.DocumentLocation;
048import org.nuxeo.ecm.core.api.DocumentModel;
049import org.nuxeo.ecm.core.api.DocumentNotFoundException;
050import org.nuxeo.ecm.core.api.IdRef;
051import org.nuxeo.ecm.core.api.LifeCycleException;
052import org.nuxeo.ecm.core.api.NuxeoException;
053import org.nuxeo.ecm.core.api.PathRef;
054import org.nuxeo.ecm.core.api.PropertyException;
055import org.nuxeo.ecm.core.api.impl.DocumentLocationImpl;
056import org.nuxeo.ecm.core.api.model.DocumentPart;
057import org.nuxeo.ecm.core.api.model.Property;
058import org.nuxeo.ecm.core.api.security.SecurityConstants;
059import org.nuxeo.ecm.core.io.download.DownloadService;
060import org.nuxeo.ecm.core.lifecycle.LifeCycle;
061import org.nuxeo.ecm.core.lifecycle.LifeCycleService;
062import org.nuxeo.ecm.core.schema.FacetNames;
063import org.nuxeo.ecm.core.schema.SchemaManager;
064import org.nuxeo.ecm.core.schema.types.Field;
065import org.nuxeo.ecm.core.schema.types.ListType;
066import org.nuxeo.ecm.core.schema.types.Schema;
067import org.nuxeo.ecm.core.schema.types.Type;
068import org.nuxeo.ecm.core.utils.DocumentModelUtils;
069import org.nuxeo.ecm.directory.DirectoryException;
070import org.nuxeo.ecm.directory.Session;
071import org.nuxeo.ecm.directory.api.DirectoryService;
072import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeEntry;
073import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry;
074import org.nuxeo.ecm.platform.types.TypeManager;
075import org.nuxeo.ecm.platform.types.adapter.TypeInfo;
076import org.nuxeo.ecm.platform.ui.web.directory.DirectoryFunctions;
077import org.nuxeo.ecm.platform.ui.web.directory.DirectoryHelper;
078import org.nuxeo.ecm.platform.ui.web.rest.RestHelper;
079import org.nuxeo.ecm.platform.ui.web.rest.api.URLPolicyService;
080import org.nuxeo.ecm.platform.ui.web.util.BaseURL;
081import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils;
082import org.nuxeo.ecm.platform.url.DocumentViewImpl;
083import org.nuxeo.ecm.platform.url.api.DocumentView;
084import org.nuxeo.ecm.platform.url.codec.DocumentFileCodec;
085import org.nuxeo.runtime.api.Framework;
086
087/**
088 * Document model functions.
089 *
090 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
091 * @author <a href="mailto:og@nuxeo.com">Olivier Grisel</a>
092 */
093public final class DocumentModelFunctions implements LiveEditConstants {
094
095    private static final Log log = LogFactory.getLog(DocumentModelFunctions.class);
096
097    private static final String JSESSIONID = "JSESSIONID";
098
099    private static final String i18n_prefix = "%i18n";
100
101    private static final String NXEDIT_URL_VIEW_ID = "nxliveedit.faces";
102
103    private static final String NXEDIT_URL_SCHEME = "nxedit";
104
105    private static MimetypeRegistry mimetypeService;
106
107    private static TypeManager typeManagerService;
108
109    private static DirectoryService dirService;
110
111    private static LifeCycleService lifeCycleService;
112
113    // static cache of default viewId per document type shared all among
114    // threads
115    private static final Map<String, String> defaultViewCache = Collections.synchronizedMap(new HashMap<String, String>());
116
117    // Utility class.
118    private DocumentModelFunctions() {
119    }
120
121    private static DirectoryService getDirectoryService() {
122        if (dirService == null) {
123            dirService = DirectoryHelper.getDirectoryService();
124        }
125        return dirService;
126    }
127
128    private static MimetypeRegistry getMimetypeService() {
129        if (mimetypeService == null) {
130            mimetypeService = Framework.getService(MimetypeRegistry.class);
131        }
132        return mimetypeService;
133    }
134
135    private static TypeManager getTypeManager() {
136        if (typeManagerService == null) {
137            typeManagerService = Framework.getService(TypeManager.class);
138        }
139        return typeManagerService;
140    }
141
142    private static String getDefaultView(DocumentModel doc) {
143        String docType = doc.getType();
144
145        if (defaultViewCache.containsKey(docType)) {
146            return defaultViewCache.get(docType);
147        } else {
148            org.nuxeo.ecm.platform.types.Type type = getTypeManager().getType(docType);
149            if (type == null) {
150                return null;
151            }
152            String defaultView = type.getDefaultView();
153            defaultViewCache.put(docType, defaultView);
154            return defaultView;
155        }
156    }
157
158    private static LifeCycleService geLifeCycleService() {
159        if (lifeCycleService == null) {
160            lifeCycleService = NXCore.getLifeCycleService();
161            if (lifeCycleService == null) {
162                log.error("No Life Cycle service registered");
163            }
164        }
165        return lifeCycleService;
166    }
167
168    public static TypeInfo typeInfo(DocumentModel document) {
169        if (document != null) {
170            return document.getAdapter(TypeInfo.class);
171        } else {
172            return null;
173        }
174    }
175
176    public static String typeLabel(DocumentModel document) {
177        String label = "";
178        if (document != null) {
179            TypeInfo typeInfo = document.getAdapter(TypeInfo.class);
180            if (typeInfo != null) {
181                label = typeInfo.getLabel();
182            }
183        }
184        return label;
185    }
186
187    public static String typeView(DocumentModel document, String viewId) {
188        String viewValue = "";
189        if (document != null) {
190            TypeInfo typeInfo = document.getAdapter(TypeInfo.class);
191            if (typeInfo != null) {
192                viewValue = typeInfo.getView(viewId);
193            }
194        }
195        return viewValue;
196    }
197
198    public static String iconPath(DocumentModel document) {
199        String iconPath = "";
200        if (document != null) {
201            try {
202                iconPath = (String) document.getProperty("common", "icon");
203            } catch (PropertyException e) {
204                iconPath = null;
205            }
206            if (iconPath == null || iconPath.length() == 0 || document.getType().equals("Workspace")) {
207                TypeInfo typeInfo = document.getAdapter(TypeInfo.class);
208                if (typeInfo != null) {
209                    iconPath = typeInfo.getIcon();
210                }
211            }
212        }
213        return iconPath;
214    }
215
216    public static String iconExpandedPath(DocumentModel document) {
217        String iconPath = "";
218        if (document != null) {
219            try {
220                iconPath = (String) document.getProperty("common", "icon-expanded");
221            } catch (PropertyException e) {
222                iconPath = null;
223            }
224            if (iconPath == null || iconPath.length() == 0) {
225                TypeInfo typeInfo = document.getAdapter(TypeInfo.class);
226                if (typeInfo != null) {
227                    iconPath = typeInfo.getIconExpanded();
228                    // Set to default icon if expanded is not set
229                    if (iconPath == null || iconPath.equals("")) {
230                        iconPath = iconPath(document);
231                    }
232                }
233
234            }
235        }
236        return iconPath;
237    }
238
239    public static String bigIconPath(DocumentModel document) {
240        String iconPath = "";
241        if (document != null) {
242            TypeInfo typeInfo = document.getAdapter(TypeInfo.class);
243            if (typeInfo != null) {
244                iconPath = typeInfo.getBigIcon();
245            }
246        }
247        return iconPath;
248    }
249
250    public static String bigIconExpandedPath(DocumentModel document) {
251        String iconPath = "";
252        if (document != null) {
253            TypeInfo typeInfo = document.getAdapter(TypeInfo.class);
254            if (typeInfo != null) {
255                iconPath = typeInfo.getIconExpanded();
256                // Set to default icon if expanded is not set
257                if (iconPath == null || iconPath.equals("")) {
258                    iconPath = bigIconPath(document);
259                }
260            }
261        }
262        return iconPath;
263    }
264
265    public static String fileIconPath(Blob blob) {
266        String iconPath = "";
267        if (blob != null) {
268            MimetypeEntry mimeEntry = getMimetypeService().getMimetypeEntryByMimeType(blob.getMimeType());
269            if (mimeEntry != null) {
270                if (mimeEntry.getIconPath() != null) {
271                    // FIXME: above Context should find it
272                    iconPath = "/icons/" + mimeEntry.getIconPath();
273                }
274            }
275        }
276        return iconPath;
277    }
278
279    public static String titleOrId(DocumentModel document) {
280        String title = null;
281        if (document != null) {
282            try {
283                title = (String) document.getProperty("dublincore", "title");
284            } catch (PropertyException e) {
285                title = null;
286            }
287            if (title == null || title.length() == 0) {
288                // handle root document title
289                if ("/".equals(document.getPathAsString())) {
290                    title = "/";
291                } else {
292                    title = document.getId();
293                }
294            }
295        }
296
297        if (title == null) {
298            title = "<Unknown>";
299        }
300
301        if (title.startsWith(i18n_prefix)) {
302            String i18nTitle = title.substring(i18n_prefix.length());
303            FacesContext context = FacesContext.getCurrentInstance();
304            title = ComponentUtils.translate(context, i18nTitle);
305        }
306        return title;
307    }
308
309    /**
310     * @since 6.0
311     */
312    public static String titleFromId(final String documentId) {
313        final CoreSession coreSession = (CoreSession) Component.getInstance("documentManager");
314        if (StringUtils.isNotBlank(documentId)) {
315            try {
316                DocumentModel doc = coreSession.getDocument(new IdRef(documentId));
317                if (doc != null) {
318                    return titleOrId(doc);
319                }
320            } catch (DocumentNotFoundException e) {
321                log.info("Could not find document with id " + documentId);
322            }
323        }
324        return documentId;
325    }
326
327    public static boolean isDocumentModel(Object value) {
328        return value instanceof DocumentModel;
329    }
330
331    public static boolean isDirty(DocumentModel doc) {
332        if (doc == null) {
333            return false;
334        }
335        for (DocumentPart part : doc.getParts()) {
336            if (part.isDirty()) {
337                // check if dirty properties are not empty
338                Iterator<Property> props = part.getDirtyChildren();
339                if (props != null) {
340                    while (props.hasNext()) {
341                        Property prop = props.next();
342                        Serializable value = prop.getValue();
343                        if (value != null) {
344                            if (isPropertyValueDirty(value)) {
345                                return true;
346                            }
347                        }
348                    }
349                }
350            }
351        }
352        return false;
353    }
354
355    @SuppressWarnings("rawtypes")
356    protected static boolean isPropertyValueDirty(Object value) {
357        if (value instanceof String) {
358            if (!StringUtils.isBlank((String) value)) {
359                return true;
360            }
361        } else if (value instanceof List) {
362            List<?> list = (List) value;
363            if (!list.isEmpty()) {
364                return true;
365            }
366        } else if (value instanceof Collection) {
367            Collection<?> col = (Collection) value;
368            if (!col.isEmpty()) {
369                return true;
370            }
371        } else if (value instanceof Object[]) {
372            Object[] col = (Object[]) value;
373            if (col.length > 0) {
374                return true;
375            }
376        } else if (value instanceof Map) {
377            Map<?, ?> map = (Map) value;
378            if (map.isEmpty()) {
379                return true;
380            }
381            for (Object mapItem : map.values()) {
382                if (isPropertyValueDirty(mapItem)) {
383                    return true;
384                }
385            }
386        } else if (value != null) {
387            // in any other case, test if object is null (int, calendar,
388            // etc...)
389            return true;
390        }
391        return false;
392    }
393
394    public static boolean hasPermission(DocumentModel document, String permission) {
395        if (document == null) {
396            return false;
397        }
398        CoreSession session = document.getCoreSession();
399        if (session == null) {
400            session = (CoreSession) Component.getInstance("documentManager", ScopeType.CONVERSATION);
401        }
402        if (session == null) {
403            log.error("Cannot retrieve CoreSession for " + document);
404            return false;
405        }
406        boolean granted = session.hasPermission(document.getRef(), permission);
407        return granted;
408    }
409
410    /**
411     * Returns true if document can be modified.
412     * <p>
413     * A document can be modified if current user has 'Write' permission on it and document is mutable (no archived
414     * version).
415     *
416     * @param document
417     * @return true if document can be modified.
418     */
419    public static boolean canModify(DocumentModel document) {
420        if (document == null) {
421            return false;
422        }
423        return hasPermission(document, SecurityConstants.WRITE) && !document.hasFacet(FacetNames.IMMUTABLE);
424    }
425
426    /**
427     * Returns the default value for given schema field.
428     *
429     * @param schemaName the schema name
430     * @param fieldName the field name
431     * @return the default value.
432     * @deprecated use defaultValue(propertyName) instead
433     */
434    @Deprecated
435    public static Object defaultValue(String schemaName, String fieldName) {
436        Object value = null;
437        SchemaManager tm = Framework.getService(SchemaManager.class);
438        Schema schema = tm.getSchema(schemaName);
439        Field field = schema.getField(fieldName);
440        Type type = field.getType();
441        if (type.isListType()) {
442            Type itemType = ((ListType) type).getFieldType();
443            value = itemType.newInstance();
444        }
445        return value;
446    }
447
448    /**
449     * Returns the default value for given property name.
450     *
451     * @param propertyName as xpath
452     * @return the default value.
453     */
454    public static Object defaultValue(String propertyName) {
455        SchemaManager tm = Framework.getService(SchemaManager.class);
456        Field field = tm.getField(propertyName);
457        Object value = null;
458        if (field != null) {
459            Type type = field.getType();
460            if (type.isListType()) {
461                Type itemType = ((ListType) type).getFieldType();
462                value = itemType.newInstance();
463            } else if (type.isComplexType()) {
464                value = type.newInstance();
465            } else {
466                value = field.getDefaultValue();
467            }
468        }
469        return value;
470    }
471
472    /**
473     * @since 6.0
474     */
475    public static String fileUrl(String baseURL, String patternName, DocumentModel doc, String blobPropertyName,
476            String filename) {
477        if (doc == null) {
478            return null;
479        }
480        DocumentLocation docLoc = new DocumentLocationImpl(doc);
481        Map<String, String> params = new HashMap<String, String>();
482        params.put(DocumentFileCodec.FILE_PROPERTY_PATH_KEY, blobPropertyName);
483        params.put(DocumentFileCodec.FILENAME_KEY, filename);
484        DocumentView docView = new DocumentViewImpl(docLoc, null, params);
485
486        // generate url
487        URLPolicyService service = Framework.getService(URLPolicyService.class);
488        if (patternName == null) {
489            patternName = service.getDefaultPatternName();
490        }
491        return service.getUrlFromDocumentView(patternName, docView, baseURL);
492    }
493
494    public static String fileUrl(String patternName, DocumentModel doc, String blobPropertyName, String filename) {
495        return fileUrl(BaseURL.getBaseURL(), patternName, doc, blobPropertyName, filename);
496    }
497
498    public static String bigFileUrl(DocumentModel doc, String blobPropertyName, String filename) {
499        if (doc == null) {
500            return null;
501        }
502        DownloadService downloadService = Framework.getService(DownloadService.class);
503        return BaseURL.getBaseURL() + downloadService.getDownloadUrl(doc, blobPropertyName, filename);
504    }
505
506    public static String fileDescription(DocumentModel document, String blobPropertyName, String filePropertyName,
507            String filename) {
508        String fileInfo = "";
509        if (document != null) {
510            Long blobLength = null;
511            try {
512                Blob blob = (Blob) document.getPropertyValue(blobPropertyName);
513                if (blob != null) {
514                    blobLength = blob.getLength();
515                }
516            } catch (PropertyException e) {
517                // no prop by that name with that type
518            }
519            if (filename == null && filePropertyName != null) {
520                try {
521                    filename = (String) document.getPropertyValue(filePropertyName);
522                } catch (PropertyException e) {
523                    // no prop by that name with that type
524                }
525            }
526            if (blobLength != null && filename != null) {
527                fileInfo = filename + " [" + Functions.printFileSize(String.valueOf(blobLength)) + "]";
528            } else if (blobLength != null) {
529                fileInfo = "[" + Functions.printFileSize(String.valueOf(blobLength)) + "]";
530            } else if (filename != null) {
531                fileInfo = filename;
532            }
533        }
534        return fileInfo;
535    }
536
537    /**
538     * Convenient method to get the REST URL of a blob inside the <code>Files</code> schema.
539     *
540     * @param patternName
541     * @param doc The document model.
542     * @param index index of the element containing the blob. <code>index</code> starts at 0.
543     * @param filename The filename of the blob.
544     * @return the REST URL for the blob, or <code>null</code> if an error occurred.
545     */
546    public static String complexFileUrl(String patternName, DocumentModel doc, int index, String filename) {
547        return complexFileUrl(patternName, doc, "files:files", index, DEFAULT_SUB_BLOB_FIELD, filename);
548    }
549
550    /**
551     * Get the REST URL for a blob inside a list of complex type. For instance,
552     * <code>http://localhost/nuxeo/nxfile/server/docId/files:files%5B0%5D/file/image.png</code> for the blob property
553     * 'file' of the first element inside the 'files:files' list.
554     *
555     * @param patternName
556     * @param doc The document model.
557     * @param listElement Element containing a list of complex type.
558     * @param index Index of the element containing the blob inside the list. <code>index</code> starts at 0.
559     * @param blobPropertyName The property containing the blob.
560     * @param filename Filename of the blob.
561     * @return the REST URL for the blob, or <code>null</code> if an error occurred.
562     */
563    public static String complexFileUrl(String patternName, DocumentModel doc, String listElement, int index,
564            String blobPropertyName, String filename) {
565        DocumentLocation docLoc = new DocumentLocationImpl(doc);
566        Map<String, String> params = new HashMap<String, String>();
567
568        String fileProperty = getPropertyPath(listElement, index, blobPropertyName);
569
570        params.put(DocumentFileCodec.FILE_PROPERTY_PATH_KEY, fileProperty);
571        params.put(DocumentFileCodec.FILENAME_KEY, filename);
572        DocumentView docView = new DocumentViewImpl(docLoc, null, params);
573
574        // generate url
575        URLPolicyService service = Framework.getService(URLPolicyService.class);
576        if (patternName == null) {
577            patternName = service.getDefaultPatternName();
578        }
579        return service.getUrlFromDocumentView(patternName, docView, BaseURL.getBaseURL());
580    }
581
582    // TODO: add method accepting filename property name (used for edit online)
583
584    public static String documentUrl(DocumentModel doc, HttpServletRequest req) {
585        return documentUrl(null, doc, null, null, false);
586    }
587
588    public static String documentUrl(DocumentModel doc) {
589        return documentUrl(null, doc, null, null, false);
590    }
591
592    /**
593     * @deprecated since 7.3, use {@link #documentUrl(String, DocumentLocation, String, Map, boolean, String)} instead.
594     */
595    public static String documentUrl(String patternName, DocumentModel doc, String viewId,
596            Map<String, String> parameters, boolean newConversation) {
597        return documentUrl(patternName, doc, viewId, parameters, newConversation, (HttpServletRequest) null);
598    }
599
600    /**
601     * @deprecated since 7.3, use {@link #documentUrl(String, DocumentLocation, String, Map, boolean, String)} instead.
602     */
603    public static String documentUrl(String patternName, DocumentModel doc, String viewId,
604            Map<String, String> parameters, boolean newConversation, HttpServletRequest req) {
605        String baseURL = null;
606        if (req == null) {
607            baseURL = BaseURL.getBaseURL();
608        } else {
609            baseURL = BaseURL.getBaseURL(req);
610        }
611        return documentUrl(patternName, doc, viewId, parameters, newConversation, baseURL);
612    }
613
614    /**
615     * Computes a document URL.
616     *
617     * @since 7.3
618     */
619    public static String documentUrl(String patternName, DocumentModel doc, String viewId,
620            Map<String, String> parameters, boolean newConversation, String baseURL) {
621        DocumentLocation docLoc = new DocumentLocationImpl(doc);
622        if (viewId == null || viewId.length() == 0) {
623            viewId = getDefaultView(doc);
624        }
625        parameters = parameters == null ? new HashMap<String, String>() : parameters;
626
627        if (doc.isVersion()) {
628            parameters.put("version", "true");
629        }
630        return documentUrl(patternName, docLoc, viewId, parameters, newConversation, baseURL);
631    }
632
633    /**
634     * @since 5.7
635     */
636    public static String documentUrl(String patternName, DocumentLocation docLoc, String viewId,
637            Map<String, String> parameters, boolean newConversation, HttpServletRequest req) {
638        String baseURL = null;
639        if (req == null) {
640            baseURL = BaseURL.getBaseURL();
641        } else {
642            baseURL = BaseURL.getBaseURL(req);
643        }
644        return documentUrl(patternName, docLoc, viewId, parameters, newConversation, baseURL);
645    }
646
647    /**
648     * Computes a document URL.
649     *
650     * @since 7.3
651     */
652    public static String documentUrl(String patternName, DocumentLocation docLoc, String viewId,
653            Map<String, String> parameters, boolean newConversation, String baseURL) {
654        DocumentView docView = new DocumentViewImpl(docLoc, viewId, parameters);
655
656        // generate url
657        URLPolicyService service = Framework.getService(URLPolicyService.class);
658        PathRef pathRef = docLoc.getPathRef();
659        if (pathRef != null && !pathRef.toString().contains("/")) {
660            // Use "id" pattern for placeless document
661            patternName = "id";
662        } else if (patternName == null || patternName.length() == 0) {
663            patternName = service.getDefaultPatternName();
664        }
665
666        String url = service.getUrlFromDocumentView(patternName, docView, baseURL);
667
668        // pass conversation info if needed
669        if (!newConversation && url != null) {
670            url = RestHelper.addCurrentConversationParameters(url);
671        }
672
673        return url;
674    }
675
676    /**
677     * Computes an URL for a {@code repositoryName} only.
678     *
679     * @since 5.7
680     * @deprecated since 7.3, use {@link #repositoryUrl(String, String, String, Map, boolean, String)} instead.
681     */
682    public static String repositoryUrl(String patternName, String repositoryName, String viewId,
683            Map<String, String> parameters, boolean newConversation) {
684        return repositoryUrl(patternName, repositoryName, viewId, parameters, newConversation,
685                (HttpServletRequest) null);
686    }
687
688    /**
689     * Computes an URL for a {@code repositoryName} only.
690     *
691     * @since 5.7
692     * @deprecated since 7.3, use {@link #repositoryUrl(String, String, String, Map, boolean, String)} instead.
693     */
694    public static String repositoryUrl(String patternName, String repositoryName, String viewId,
695            Map<String, String> parameters, boolean newConversation, HttpServletRequest req) {
696        DocumentLocation docLoc = new DocumentLocationImpl(repositoryName, null);
697        parameters = parameters == null ? new HashMap<String, String>() : parameters;
698        return documentUrl(patternName, docLoc, viewId, parameters, newConversation, req);
699    }
700
701    /**
702     * Computes an URL for a {@code repositoryName} only.
703     *
704     * @since 7.3
705     */
706    public static String repositoryUrl(String patternName, String repositoryName, String viewId,
707            Map<String, String> parameters, boolean newConversation, String baseURL) {
708        DocumentLocation docLoc = new DocumentLocationImpl(repositoryName, null);
709        parameters = parameters == null ? new HashMap<String, String>() : parameters;
710        return documentUrl(patternName, docLoc, viewId, parameters, newConversation, baseURL);
711    }
712
713    protected static void addQueryParameter(StringBuilder sb, String name, String value, boolean isFirst)
714            {
715        if (isFirst) {
716            sb.append("?");
717        } else {
718            sb.append("&");
719        }
720        if (value == null) {
721            return;
722        }
723        sb.append(name);
724        sb.append("=");
725        try {
726            sb.append(URLEncoder.encode(value, URL_ENCODE_CHARSET));
727        } catch (UnsupportedEncodingException e) {
728            throw new NuxeoException("Could not encode URL parameter: " + name + "=" + value, e);
729        }
730    }
731
732    /**
733     * Build the nxedit URL for the "edit existing document" use case for a document using the file:content field as
734     * Blob holder
735     *
736     * @return the encoded URL string
737     */
738    public static String liveEditUrl(DocumentModel doc) {
739        return liveEditUrl(doc, DEFAULT_SCHEMA, DEFAULT_BLOB_FIELD, DEFAULT_FILENAME_FIELD);
740    }
741
742    /**
743     * Build the nxedit URL for the "edit existing document" use case
744     *
745     * @return the encoded URL string
746     */
747    public static String liveEditUrl(DocumentModel doc, String schemaName, String blobFieldName,
748            String filenameFieldName) {
749        if (doc == null) {
750            return ""; // JSF DebugUtil.printTree may call this
751        }
752        StringBuilder queryParamBuilder = new StringBuilder();
753        addQueryParameter(queryParamBuilder, ACTION, ACTION_EDIT_DOCUMENT, true);
754        addQueryParameter(queryParamBuilder, REPO_ID, doc.getRepositoryName(), false);
755        addQueryParameter(queryParamBuilder, DOC_REF, doc.getRef().toString(), false);
756        if (schemaName == null || "".equals(schemaName)) {
757            // try to extract it from blob field name
758            schemaName = DocumentModelUtils.getSchemaName(blobFieldName);
759            blobFieldName = DocumentModelUtils.getFieldName(blobFieldName);
760            filenameFieldName = DocumentModelUtils.getFieldName(filenameFieldName);
761        }
762        addQueryParameter(queryParamBuilder, SCHEMA, schemaName, false);
763        addQueryParameter(queryParamBuilder, BLOB_FIELD, blobFieldName, false);
764        addQueryParameter(queryParamBuilder, FILENAME_FIELD, filenameFieldName, false);
765        return buildNxEditUrl(queryParamBuilder.toString());
766    }
767
768    /**
769     * Build the nxedit URL for the "edit existing document" use case
770     *
771     * @return the encoded URL string
772     */
773    public static String complexLiveEditUrl(DocumentModel doc, String listPropertyName, int index,
774            String blobPropertyName, String filenamePropertyName) {
775
776        StringBuilder queryParamBuilder = new StringBuilder();
777        addQueryParameter(queryParamBuilder, ACTION, ACTION_EDIT_DOCUMENT, true);
778        addQueryParameter(queryParamBuilder, REPO_ID, doc.getRepositoryName(), false);
779        addQueryParameter(queryParamBuilder, DOC_REF, doc.getRef().toString(), false);
780        addQueryParameter(queryParamBuilder, BLOB_PROPERTY_NAME,
781                getPropertyPath(listPropertyName, index, blobPropertyName), false);
782        addQueryParameter(queryParamBuilder, FILENAME_PROPERTY_NAME,
783                getPropertyPath(listPropertyName, index, filenamePropertyName), false);
784        return buildNxEditUrl(queryParamBuilder.toString());
785    }
786
787    /**
788     * Build the nxedit URL for the "create new document" use case with a document using the file:content field as Blob
789     * holder
790     *
791     * @param mimetype the mime type of the newly created document
792     * @return the encoded URL string
793     */
794    public static String liveCreateUrl(String mimetype) {
795        return liveCreateUrl(mimetype, DEFAULT_DOCTYPE, DEFAULT_SCHEMA, DEFAULT_BLOB_FIELD, DEFAULT_FILENAME_FIELD);
796    }
797
798    /**
799     * Build the nxedit URL for the "create new document" use case
800     *
801     * @param mimetype the mime type of the newly created document
802     * @param docType the document type of the document to create
803     * @param schemaName the schema of the blob to hold the new attachment
804     * @param blobFieldName the field name of the blob to hold the new attachment
805     * @param filenameFieldName the field name of the filename of the new attachment
806     * @return the encoded URL string
807     */
808    public static String liveCreateUrl(String mimetype, String docType, String schemaName, String blobFieldName,
809            String filenameFieldName) {
810
811        StringBuilder queryParamBuilder = new StringBuilder();
812        addQueryParameter(queryParamBuilder, ACTION, ACTION_CREATE_DOCUMENT, true);
813        addQueryParameter(queryParamBuilder, MIMETYPE, mimetype, false);
814        addQueryParameter(queryParamBuilder, SCHEMA, schemaName, false);
815        addQueryParameter(queryParamBuilder, BLOB_FIELD, blobFieldName, false);
816        addQueryParameter(queryParamBuilder, FILENAME_FIELD, filenameFieldName, false);
817        addQueryParameter(queryParamBuilder, DOC_TYPE, docType, false);
818        return buildNxEditUrl(queryParamBuilder.toString());
819    }
820
821    /**
822     * Build the nxedit URL for the "create new document from template" use case with "File" doc type and "file" schema
823     *
824     * @param template the document holding the blob to be used as template
825     * @return the encoded URL string
826     */
827    public static String liveCreateFromTemplateUrl(DocumentModel template) {
828        return liveCreateFromTemplateUrl(template, DEFAULT_SCHEMA, DEFAULT_BLOB_FIELD, DEFAULT_DOCTYPE, DEFAULT_SCHEMA,
829                DEFAULT_BLOB_FIELD, DEFAULT_FILENAME_FIELD);
830    }
831
832    /**
833     * Build the nxedit URL for the "create new document from template" use case
834     *
835     * @param template the document holding the blob to be used as template
836     * @param templateSchemaName the schema of the blob holding the template
837     * @param templateBlobFieldName the field name of the blob holding the template
838     * @param docType the document type of the new document to create
839     * @param schemaName the schema of the new blob to be saved as attachment
840     * @param blobFieldName the field name of the new blob to be saved as attachment
841     * @param filenameFieldName the field name of the filename of the attachment
842     * @return the encoded URL string
843     */
844    public static String liveCreateFromTemplateUrl(DocumentModel template, String templateSchemaName,
845            String templateBlobFieldName, String docType, String schemaName, String blobFieldName,
846            String filenameFieldName) {
847
848        StringBuilder queryParamBuilder = new StringBuilder();
849        addQueryParameter(queryParamBuilder, ACTION, ACTION_CREATE_DOCUMENT_FROM_TEMPLATE, true);
850        addQueryParameter(queryParamBuilder, TEMPLATE_REPO_ID, template.getRepositoryName(), false);
851        addQueryParameter(queryParamBuilder, TEMPLATE_DOC_REF, template.getRef().toString(), false);
852        addQueryParameter(queryParamBuilder, TEMPLATE_SCHEMA, templateSchemaName, false);
853        addQueryParameter(queryParamBuilder, TEMPLATE_BLOB_FIELD, templateBlobFieldName, false);
854        addQueryParameter(queryParamBuilder, SCHEMA, schemaName, false);
855        addQueryParameter(queryParamBuilder, BLOB_FIELD, blobFieldName, false);
856        addQueryParameter(queryParamBuilder, FILENAME_FIELD, filenameFieldName, false);
857        addQueryParameter(queryParamBuilder, DOC_TYPE, docType, false);
858        return buildNxEditUrl(queryParamBuilder.toString());
859    }
860
861    private static String buildNxEditUrl(String queryParameters) {
862        FacesContext context = FacesContext.getCurrentInstance();
863        HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
864
865        // build the URL prefix by concatenating nxedit: scheme with the
866        // http:// or https:// base URL from the current request context and
867        // the LiveEditBoostrapHelper JSF view
868        StringBuilder nxeditUrlBuilder = new StringBuilder(NXEDIT_URL_SCHEME);
869        nxeditUrlBuilder.append(":");
870        nxeditUrlBuilder.append(BaseURL.getBaseURL(request));
871        nxeditUrlBuilder.append(NXEDIT_URL_VIEW_ID);
872
873        // add the query parameters them selves
874        nxeditUrlBuilder.append(queryParameters);
875
876        // add seam conversation and JSESSION ids
877        addQueryParameter(nxeditUrlBuilder, Manager.instance().getConversationIdParameter(),
878                Manager.instance().getCurrentConversationId(), false);
879        addQueryParameter(nxeditUrlBuilder, JSESSIONID, extractJSessionId(request), false);
880        return nxeditUrlBuilder.toString();
881    }
882
883    /**
884     * Extract the current JSESSIONID value from the request context
885     *
886     * @param request the current HttpServletRequest request
887     * @return the current JSESSIONID string
888     */
889    public static String extractJSessionId(HttpServletRequest request) {
890        if (request.getSession() != null) {
891            return request.getSession().getId();
892        }
893        if (request.getCookies() != null) {
894            for (Cookie cookie : request.getCookies()) {
895                if (cookie.getName().equalsIgnoreCase("jsessionid")) {
896                    return cookie.getValue();
897                }
898            }
899        }
900        return null;
901    }
902
903    /**
904     * Returns the label for given directory and id.
905     *
906     * @param directoryName the directory name
907     * @param id the label id
908     * @return the label.
909     * @throws DirectoryException
910     * @deprecated use {@link DirectoryFunctions#getDirectoryEntry(String, String)}
911     */
912    @Deprecated
913    public static String getLabelFromId(String directoryName, String id) throws DirectoryException {
914        if (id == null) {
915            return "";
916        }
917        try (Session directory = getDirectoryService().open(directoryName)) {
918            // XXX hack, directory entries have only one datamodel
919            DocumentModel documentModel = directory.getEntry(id);
920            String schemaName = documentModel.getSchemas()[0];
921            return (String) documentModel.getProperty(schemaName, "label");
922        } catch (PropertyException e) {
923            return "";
924        }
925    }
926
927    public static String getPropertyPath(String listPropertyName, int index, String subPropertyName) {
928        return listPropertyName + "/" + index + "/" + subPropertyName;
929    }
930
931    /**
932     * Returns all the available transitions given the current state.
933     *
934     * @param lifeCycleName the Life Cycle name
935     * @param currentState the state from which the transitions should start
936     * @since 5.4.2
937     */
938    public static Collection<String> getAvailableLifeCycleTransitions(String lifeCycleName, String currentState)
939            throws LifeCycleException {
940        LifeCycle lf = geLifeCycleService().getLifeCycleByName(lifeCycleName);
941        return lf.getAllowedStateTransitionsFrom(currentState);
942    }
943
944    /**
945     * Reset default view cache.
946     *
947     * @since 5.8
948     */
949    public static void resetDefaultViewCache() {
950        defaultViewCache.clear();
951    }
952
953}