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