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