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