001/*
002 * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     <a href="mailto:tdelprat@nuxeo.com">Tiry</a>
018 *     <a href="mailto:grenard@nuxeo.com">Guillaume</a>
019 */
020
021package org.nuxeo.ecm.platform.ui.select2;
022
023import java.io.BufferedOutputStream;
024import java.io.IOException;
025import java.io.Serializable;
026import java.util.ArrayList;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Locale;
030import java.util.Map;
031import java.util.Map.Entry;
032
033import javax.faces.context.FacesContext;
034import javax.faces.event.PhaseId;
035import javax.servlet.http.HttpServletRequest;
036
037import net.sf.json.JSONArray;
038import net.sf.json.JSONObject;
039
040import org.apache.commons.io.output.ByteArrayOutputStream;
041import org.apache.commons.lang.StringUtils;
042import org.apache.commons.logging.Log;
043import org.apache.commons.logging.LogFactory;
044import org.codehaus.jackson.JsonGenerator;
045import org.jboss.seam.ScopeType;
046import org.jboss.seam.annotations.Destroy;
047import org.jboss.seam.annotations.In;
048import org.jboss.seam.annotations.Name;
049import org.jboss.seam.annotations.Scope;
050import org.jboss.seam.contexts.FacesLifecycle;
051import org.nuxeo.ecm.automation.AutomationService;
052import org.nuxeo.ecm.automation.InvalidChainException;
053import org.nuxeo.ecm.automation.OperationContext;
054import org.nuxeo.ecm.automation.OperationException;
055import org.nuxeo.ecm.automation.core.operations.services.DocumentPageProviderOperation;
056import org.nuxeo.ecm.automation.core.operations.services.directory.SuggestDirectoryEntries;
057import org.nuxeo.ecm.automation.core.operations.users.SuggestUserEntries;
058import org.nuxeo.ecm.automation.features.SuggestConstants;
059import org.nuxeo.ecm.automation.jaxrs.io.JsonHelper;
060import org.nuxeo.ecm.core.api.CoreInstance;
061import org.nuxeo.ecm.core.api.CoreSession;
062import org.nuxeo.ecm.core.api.DocumentModel;
063import org.nuxeo.ecm.core.api.DocumentModelList;
064import org.nuxeo.ecm.core.api.DocumentRef;
065import org.nuxeo.ecm.core.api.IdRef;
066import org.nuxeo.ecm.core.api.NuxeoGroup;
067import org.nuxeo.ecm.core.api.NuxeoPrincipal;
068import org.nuxeo.ecm.core.api.PathRef;
069import org.nuxeo.ecm.core.api.PropertyException;
070import org.nuxeo.ecm.core.api.repository.RepositoryManager;
071import org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonWriter;
072import org.nuxeo.ecm.core.io.registry.MarshallerRegistry;
073import org.nuxeo.ecm.core.io.registry.context.RenderingContext;
074import org.nuxeo.ecm.core.schema.SchemaManager;
075import org.nuxeo.ecm.core.schema.types.Field;
076import org.nuxeo.ecm.core.schema.types.QName;
077import org.nuxeo.ecm.core.schema.types.Schema;
078import org.nuxeo.ecm.directory.Directory;
079import org.nuxeo.ecm.directory.DirectoryException;
080import org.nuxeo.ecm.directory.Session;
081import org.nuxeo.ecm.directory.api.DirectoryService;
082import org.nuxeo.ecm.platform.forms.layout.api.Widget;
083import org.nuxeo.ecm.platform.forms.layout.api.WidgetTypeConfiguration;
084import org.nuxeo.ecm.platform.forms.layout.api.WidgetTypeDefinition;
085import org.nuxeo.ecm.platform.forms.layout.api.service.LayoutStore;
086import org.nuxeo.ecm.platform.ui.select2.common.Select2Common;
087import org.nuxeo.ecm.platform.url.DocumentViewImpl;
088import org.nuxeo.ecm.platform.url.codec.DocumentIdCodec;
089import org.nuxeo.ecm.platform.usermanager.UserManager;
090import org.nuxeo.ecm.webengine.jaxrs.coreiodelegate.RenderingContextWebUtils;
091import org.nuxeo.runtime.api.Framework;
092
093/**
094 * Initialization for Select2.
095 *
096 * @since 5.7.3
097 */
098@Name("select2Actions")
099@Scope(ScopeType.EVENT)
100public class Select2ActionsBean implements Serializable {
101
102    private static final long serialVersionUID = 1L;
103
104    private static final Log log = LogFactory.getLog(Select2ActionsBean.class);
105
106    protected static final String SELECT2_RESOURCES_MARKER = "SELECT2_RESOURCES_MARKER";
107
108    private static List<String> formatList(JSONArray array) {
109        List<String> result = new ArrayList<String>();
110        if (array != null) {
111            for (int i = 0; i < array.size(); i++) {
112                result.add(array.getJSONObject(i).getString(Select2Common.LABEL));
113            }
114        }
115        return result;
116    }
117
118    @In(create = true)
119    protected Map<String, String> messages;
120
121    @In(create = true, required = false)
122    protected transient CoreSession documentManager;
123
124    protected transient CoreSession dedicatedSession = null;
125
126    @Destroy
127    public void destroy() {
128        if (dedicatedSession != null) {
129            dedicatedSession.close();
130        }
131    }
132
133    private static Map<String, String> getDefaultFormattersMap(final String suggestionFormatterName,
134            String selectionFormatterName) {
135        Map<String, String> result = new HashMap<String, String>();
136        result.put(Select2Common.SUGGESTION_FORMATTER, suggestionFormatterName);
137        result.put(Select2Common.SELECTION_FORMATTER, selectionFormatterName);
138        return result;
139    }
140
141    /**
142     * @since 5.9.3
143     */
144    protected static Map<String, String> getContextParameter(final DocumentModel doc) {
145        DocumentIdCodec documentIdCodec = new DocumentIdCodec();
146        Map<String, String> contextParameters = new HashMap<String, String>();
147        contextParameters.put("documentURL", documentIdCodec.getUrlFromDocumentView(new DocumentViewImpl(doc)));
148        return contextParameters;
149    }
150
151    public String encodeParametersForUserSuggestion(final Widget widget,
152            final Map<String, Serializable> resolvedWidgetProperties) {
153        Map<String, String> params = getDefaultFormattersMap(Select2Common.USER_DEFAULT_SUGGESTION_FORMATTER,
154                Select2Common.USER_DEFAULT_SELECTION_FORMATTER);
155        params.put(Select2Common.OPERATION_ID, SuggestUserEntries.ID);
156        return encodeParameters(widget, params, resolvedWidgetProperties);
157    }
158
159    public String encodeParametersForDirectory(final Widget widget,
160            final Map<String, Serializable> resolvedWidgetProperties) {
161        Map<String, String> params = getDefaultFormattersMap(Select2Common.DIR_DEFAULT_SUGGESTION_FORMATTER,
162                Select2Common.DIR_DEFAULT_SELECTION_FORMATTER);
163        params.put(Select2Common.OPERATION_ID, SuggestDirectoryEntries.ID);
164        return encodeParameters(widget, params, resolvedWidgetProperties);
165    }
166
167    public String encodeParameters(final Widget widget) {
168        return encodeParameters(widget, null);
169    }
170
171    public String encodeParameters(final Widget widget, final Map<String, Serializable> resolvedWidgetProperties) {
172        Map<String, String> params = getDefaultFormattersMap(Select2Common.DOC_DEFAULT_SUGGESTION_FORMATTER,
173                Select2Common.DOC_DEFAULT_SELECTION_FORMATTER);
174        params.put(Select2Common.OPERATION_ID, DocumentPageProviderOperation.ID);
175        return encodeParameters(widget, params, resolvedWidgetProperties);
176    }
177
178    /**
179     * Encode widget properties and parameters that Select2 pick them up in a hidden input.
180     *
181     * @param widget the widget
182     * @return encoded
183     * @since 5.7.3
184     */
185    public String encodeParameters(final Widget widget, final Map<String, String> defaultParams,
186            final Map<String, Serializable> resolvedWidgetProperties) {
187        ByteArrayOutputStream baos = new ByteArrayOutputStream();
188        BufferedOutputStream out = new BufferedOutputStream(baos);
189
190        JsonGenerator jg;
191        try {
192            jg = JsonHelper.createJsonGenerator(out);
193
194            jg.writeStartObject();
195
196            // multiple is not in properties and we must add it because Select2
197            // needs to know.
198            jg.writeStringField("multiple", "" + isMultiSelection(widget));
199
200            final boolean isTranslated = widget.isTranslated();
201
202            // Are we writing or reading
203            boolean readonly = !widget.getMode().equals("edit") && !widget.getMode().equals("create");
204            jg.writeStringField(Select2Common.READ_ONLY_PARAM, Boolean.toString(readonly));
205
206            Map<String, Serializable> propertySet = null;
207            if (resolvedWidgetProperties != null) {
208                propertySet = resolvedWidgetProperties;
209            } else {
210                propertySet = widget.getProperties();
211            }
212
213            boolean hasPlaceholder = false;
214            boolean hasAjaxReRender = false;
215            boolean hasWidth = false;
216            boolean hasMinChars = false;
217
218            for (Entry<String, Serializable> entry : propertySet.entrySet()) {
219
220                if (entry.getValue() == null) {
221                    continue;
222                }
223
224                if (defaultParams != null) {
225                    // Widget properties have priority on default properties
226                    defaultParams.remove(entry.getKey());
227                }
228
229                String value = entry.getValue().toString();
230
231                if (entry.getKey().equals(Select2Common.PLACEHOLDER)) {
232                    hasPlaceholder = true;
233                    // placeholder can be translated
234                    if (isTranslated || Boolean.parseBoolean((String) propertySet.get("translatePlaceholder"))) {
235                        value = messages.get(entry.getValue().toString());
236                    }
237                } else if (entry.getKey().equals(Select2Common.AJAX_RERENDER)) {
238                    hasAjaxReRender = true;
239                } else if (entry.getKey().equals(Select2Common.WIDTH)) {
240                    hasWidth = true;
241                } else if (entry.getKey().equals(Select2Common.MIN_CHARS)) {
242                    hasMinChars = true;
243                }
244
245                jg.writeStringField(entry.getKey(), value);
246
247            }
248
249            if (defaultParams != null) {
250                // All default params which are not in widget properties
251                for (Entry<String, String> e : defaultParams.entrySet()) {
252                    jg.writeStringField(e.getKey(), e.getValue());
253                }
254            }
255
256            if (!hasPlaceholder) {
257                // No placeholder provider and Select2 requires one to enable
258                // the
259                // reset button.
260                jg.writeStringField(Select2Common.PLACEHOLDER, messages.get("label.vocabulary.selectValue"));
261            }
262
263            if (!hasWidth) {
264                jg.writeStringField(Select2Common.WIDTH, Select2Common.DEFAULT_WIDTH);
265            }
266
267            if (!hasMinChars) {
268                jg.writeNumberField(Select2Common.MIN_CHARS, Select2Common.DEFAULT_MIN_CHARS);
269            }
270
271            if (hasAjaxReRender) {
272                jg.writeStringField(Select2Common.RERENDER_JS_FUNCTION_NAME, widget.getId() + "_reRender");
273            }
274
275            jg.writeEndObject();
276            jg.flush();
277            out.flush();
278            return new String(baos.toByteArray(), "UTF-8");
279        } catch (IOException e) {
280            log.error("Could not encode parameters", e);
281            return null;
282        }
283    }
284
285    protected LayoutStore getLayoutStore() {
286        return Framework.getService(LayoutStore.class);
287    }
288
289    @SuppressWarnings("rawtypes")
290    protected JSONArray getMultipleDirectoryEntries(final Object value, final String directoryName,
291            final boolean localize, String keySeparator, final boolean dbl10n, final String labelFieldName) {
292        JSONArray result = new JSONArray();
293        if (value == null) {
294            return result;
295        }
296
297        List<String> storedRefs = new ArrayList<>();
298        if (value instanceof List) {
299            for (Object v : (List) value) {
300                storedRefs.add(v.toString());
301            }
302        } else if (value instanceof Object[]) {
303            for (Object v : (Object[]) value) {
304                storedRefs.add(v.toString());
305            }
306        }
307
308        DirectoryService directoryService = Framework.getLocalService(DirectoryService.class);
309        try {
310            Directory directory = directoryService.getDirectory(directoryName);
311            if (directory == null) {
312                log.error("Could not find directory with name " + directoryName);
313                return null;
314            }
315
316            try (Session session = directory.getSession()) {
317                String schemaName = directory.getSchema();
318                SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class);
319                Schema schema = schemaManager.getSchema(schemaName);
320                final Locale locale = org.jboss.seam.core.Locale.instance();
321                final String label = SuggestConstants.getLabelFieldName(schema, dbl10n, labelFieldName,
322                        locale.getLanguage());
323
324                for (String ref : storedRefs) {
325                    JSONObject obj = resolveDirectoryEntry(ref, keySeparator, session, schema, label, localize, dbl10n);
326                    if (obj != null) {
327                        result.add(obj);
328                    }
329                }
330                return result;
331            }
332        } catch (DirectoryException de) {
333            log.error("An error occured while obtaining directory " + directoryName, de);
334            return result;
335        }
336    }
337
338    @SuppressWarnings("rawtypes")
339    protected JSONArray getMultipleUserReference(final Object value, final boolean prefixed,
340            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
341            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
342            final boolean displayEmailInSuggestion, final boolean hideIcon) {
343        if (value == null) {
344            return null;
345        }
346        JSONArray result = new JSONArray();
347        List<String> storedRefs = new ArrayList<>();
348        if (value instanceof List) {
349            for (Object v : (List) value) {
350                storedRefs.add(v.toString());
351            }
352        } else if (value instanceof Object[]) {
353            for (Object v : (Object[]) value) {
354                storedRefs.add(v.toString());
355            }
356        }
357
358        for (String ref : storedRefs) {
359            JSONObject resolved = getSingleUserReference(ref, prefixed, firstLabelField, secondLabelField,
360                    thirdLabelField, hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion,
361                    hideIcon);
362            if (resolved != null && !resolved.isEmpty()) {
363                result.add(resolved);
364            }
365        }
366        return result;
367    }
368
369    protected CoreSession getRepositorySession(String repoName) {
370
371        if (repoName == null || repoName.isEmpty()) {
372            RepositoryManager rm = Framework.getLocalService(RepositoryManager.class);
373            repoName = rm.getDefaultRepositoryName();
374        }
375
376        if (documentManager != null && documentManager.getRepositoryName().equals(repoName)) {
377            return documentManager;
378        }
379
380        dedicatedSession = CoreInstance.openCoreSession(repoName);
381        return dedicatedSession;
382    }
383
384    protected JSONObject getSingleDirectoryEntry(final String storedReference, final String directoryName,
385            final boolean localize, String keySeparator, final boolean dbl10n, final String labelFieldName) {
386
387        if (storedReference == null || storedReference.isEmpty()) {
388            return null;
389        }
390
391        DirectoryService directoryService = Framework.getLocalService(DirectoryService.class);
392        try {
393            Directory directory = directoryService.getDirectory(directoryName);
394            if (directory == null) {
395                log.error("Could not find directory with name " + directoryName);
396                return null;
397            }
398
399            try (Session session = directory.getSession()) {
400                String schemaName = directory.getSchema();
401                SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class);
402                Schema schema = schemaManager.getSchema(schemaName);
403
404                final Locale locale = org.jboss.seam.core.Locale.instance();
405                final String label = SuggestConstants.getLabelFieldName(schema, dbl10n, labelFieldName, locale.getLanguage());
406
407                JSONObject obj = resolveDirectoryEntry(storedReference, keySeparator, session, schema, label, localize,
408                        dbl10n);
409
410                return obj;
411            }
412        } catch (DirectoryException de) {
413            log.error("An error occured while obtaining directory " + directoryName, de);
414            return null;
415        }
416    }
417
418    protected JSONObject getSingleUserReference(final String storedReference, final boolean prefixed,
419            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
420            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
421            final boolean displayEmailInSuggestion, final boolean hideIcon) {
422        UserManager userManager = Framework.getLocalService(UserManager.class);
423        SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class);
424        DirectoryService dirService = Framework.getLocalService(DirectoryService.class);
425        JSONObject obj = new JSONObject();
426        if (storedReference == null || storedReference.isEmpty()) {
427            return null;
428        }
429        DocumentModel user = null;
430        DocumentModel group = null;
431        Directory userDir = dirService.getDirectory(userManager.getUserDirectoryName());
432        if (prefixed) {
433            if (storedReference.startsWith(NuxeoPrincipal.PREFIX)) {
434                user = userManager.getUserModel(storedReference.substring(NuxeoPrincipal.PREFIX.length()));
435            } else if (storedReference.startsWith(NuxeoGroup.PREFIX)) {
436                group = userManager.getGroupModel(storedReference.substring(NuxeoGroup.PREFIX.length()));
437            } else {
438                log.warn("User reference is prefixed but prefix was not found on reference: " + storedReference);
439                return createNotFoundEntry(storedReference);
440            }
441        } else {
442            user = userManager.getUserModel(storedReference);
443            if (user == null) {
444                group = userManager.getGroupModel(storedReference);
445            }
446        }
447        if (user != null) {
448            Schema schema = schemaManager.getSchema(userManager.getUserSchemaName());
449            for (Field field : schema.getFields()) {
450                QName fieldName = field.getName();
451                String key = fieldName.getLocalName();
452                Serializable value = user.getPropertyValue(fieldName.getPrefixedName());
453                if (key.equals(userDir.getPasswordField())) {
454                    continue;
455                }
456                obj.element(key, value);
457            }
458            String userId = user.getId();
459            obj.put(SuggestConstants.ID, userId);
460            obj.put(SuggestConstants.TYPE_KEY_NAME, SuggestConstants.USER_TYPE);
461            obj.put(SuggestConstants.PREFIXED_ID_KEY_NAME, NuxeoPrincipal.PREFIX + userId);
462            SuggestConstants.computeUserLabel(obj, firstLabelField, secondLabelField, thirdLabelField, hideFirstLabel,
463                    hideSecondLabel, hideThirdLabel, displayEmailInSuggestion, userId);
464            SuggestConstants.computeUserGroupIcon(obj, hideIcon);
465        } else if (group != null) {
466            Schema schema = schemaManager.getSchema(userManager.getGroupSchemaName());
467            for (Field field : schema.getFields()) {
468                QName fieldName = field.getName();
469                String key = fieldName.getLocalName();
470                Serializable value = group.getPropertyValue(fieldName.getPrefixedName());
471                obj.element(key, value);
472            }
473            // If the group hasn't an label, let's put the groupid
474            String groupId = group.getId();
475            SuggestConstants.computeGroupLabel(obj, groupId, userManager.getGroupLabelField(), hideFirstLabel);
476            obj.put(SuggestConstants.ID, groupId);
477            obj.put(SuggestConstants.TYPE_KEY_NAME, SuggestConstants.GROUP_TYPE);
478            obj.put(SuggestConstants.PREFIXED_ID_KEY_NAME, NuxeoGroup.PREFIX + groupId);
479            SuggestConstants.computeUserGroupIcon(obj, hideIcon);
480        } else {
481            log.warn("Could not resolve user or group reference: " + storedReference);
482            return createNotFoundEntry(storedReference);
483        }
484        return obj;
485    }
486
487    public boolean isMultiSelection(final Widget widget) {
488        String wtCat = widget.getTypeCategory();
489        if (StringUtils.isBlank(wtCat)) {
490            wtCat = "jsf";
491        }
492        WidgetTypeDefinition wtDef = getLayoutStore().getWidgetTypeDefinition(wtCat, widget.getType());
493        if (wtDef != null) {
494            WidgetTypeConfiguration conf = wtDef.getConfiguration();
495            if (conf != null) {
496                return conf.isList();
497            }
498        }
499        return false;
500    }
501
502    /**
503     * @deprecated since 7.10: JSF resources mechanism allows to detect resources already included in the page natively.
504     */
505    @Deprecated
506    public boolean mustIncludeResources() {
507        FacesContext facesContext = FacesContext.getCurrentInstance();
508        if (facesContext != null) {
509            PhaseId currentPhaseId = FacesLifecycle.getPhaseId();
510            if (currentPhaseId.equals(PhaseId.RENDER_RESPONSE)) {
511                HttpServletRequest request = (HttpServletRequest) facesContext.getExternalContext().getRequest();
512
513                if (request.getAttribute(SELECT2_RESOURCES_MARKER) != null) {
514                    return false;
515                } else {
516                    request.setAttribute(SELECT2_RESOURCES_MARKER, "done");
517                    return true;
518                }
519            }
520        }
521        return false;
522    }
523
524    protected JSONObject createNotFoundEntry(final String id) {
525        return createEntryWithWarnMessage(id, "entry not found");
526    }
527
528    protected JSONObject createEntryWithWarnMessage(final String label, final String warnMessage) {
529        JSONObject obj = new JSONObject();
530        obj.put(SuggestConstants.ID, label);
531        obj.put(SuggestConstants.ABSOLUTE_LABEL, label);
532        obj.put(SuggestConstants.LABEL, label);
533        obj.put(SuggestConstants.WARN_MESSAGE_LABEL, warnMessage);
534        return obj;
535    }
536
537    protected JSONObject resolveDirectoryEntry(final String storedReference, String keySeparator,
538            final Session session, final Schema schema, final String label, final boolean localize, final boolean dbl10n) {
539        if (storedReference == null || storedReference.isEmpty()) {
540            log.trace("No reference provided ");
541            return null;
542        }
543
544        if (keySeparator == null || keySeparator.isEmpty()) {
545            keySeparator = SuggestConstants.DEFAULT_KEY_SEPARATOR;
546        }
547
548        String entryId = storedReference.substring(storedReference.lastIndexOf(keySeparator) + 1,
549                storedReference.length());
550
551        DocumentModel result = session.getEntry(entryId);
552        if (result == null) {
553            log.warn("Unable to resolve entry " + storedReference);
554            return createNotFoundEntry(storedReference);
555        }
556
557        JSONObject obj = new JSONObject();
558        for (Field field : schema.getFields()) {
559            QName fieldName = field.getName();
560            String key = fieldName.getLocalName();
561            Serializable value = result.getPropertyValue(fieldName.getPrefixedName());
562            if (label.equals(key)) {
563                if (localize && !dbl10n) {
564                    value = messages.get(value);
565                }
566                obj.element(SuggestConstants.LABEL, value);
567                obj.element(SuggestConstants.ABSOLUTE_LABEL,
568                        getParentAbsoluteLabel(storedReference, keySeparator, session, fieldName, localize, dbl10n));
569            } else {
570                obj.element(key, value);
571            }
572        }
573
574        // Add a warning message if the entity is obsolete
575        if (obj.containsKey(SuggestConstants.OBSOLETE_FIELD_ID) && obj.getInt(SuggestConstants.OBSOLETE_FIELD_ID) > 0) {
576            obj.element(SuggestConstants.WARN_MESSAGE_LABEL, messages.get("label.vocabulary.entry.obsolete"));
577        }
578
579        obj.element(SuggestConstants.COMPUTED_ID, storedReference);
580        return obj;
581    }
582
583    /**
584     * @since 5.9.3
585     */
586    protected String getParentAbsoluteLabel(final String entryId, final String keySeparator, final Session session,
587            final QName labelFieldName, final boolean localize, final boolean dbl10n) throws PropertyException {
588        String[] split = entryId.split(keySeparator);
589        String result = "";
590        for (int i = 0; i < split.length; i++) {
591            DocumentModel entry = session.getEntry(split[i]);
592            if (entry != null) {
593                Serializable value = entry.getPropertyValue(labelFieldName.getPrefixedName());
594                if (localize && !dbl10n) {
595                    value = messages.get(value);
596                }
597                result += (i > 0 ? "/" : "") + value;
598            }
599        }
600
601        return result;
602    }
603
604    public String resolveMultipleDirectoryEntries(final Object value, final String directoryName,
605            final boolean localize, String keySeparator, final boolean dbl10n, final String labelFieldName) {
606        JSONArray result = getMultipleDirectoryEntries(value, directoryName, localize, keySeparator, dbl10n,
607                labelFieldName);
608        if (result != null) {
609            return result.toString();
610        } else {
611            return "[]";
612        }
613    }
614
615    public List<String> resolveMultipleDirectoryEntryLabels(final Object value, final String directoryName,
616            final boolean localize, final String keySeparator, final boolean dbl10n, final String labelFieldName) {
617        return formatList(getMultipleDirectoryEntries(value, directoryName, localize, keySeparator, dbl10n,
618                labelFieldName));
619    }
620
621    @SuppressWarnings("rawtypes")
622    public List<String> resolveMultipleReferenceLabels(final Object value, final String repo,
623            final String operationName, final String idProperty, final String label) {
624
625        List<String> result = new ArrayList<>();
626
627        if (value == null) {
628            return result;
629        }
630
631        List<String> storedRefs = new ArrayList<>();
632        if (value instanceof List) {
633            for (Object v : (List) value) {
634                storedRefs.add(v.toString());
635            }
636        } else if (value instanceof Object[]) {
637            for (Object v : (Object[]) value) {
638                storedRefs.add(v.toString());
639            }
640        }
641
642        for (String ref : storedRefs) {
643            DocumentModel doc = resolveReference(repo, ref, operationName, idProperty);
644            if (doc != null) {
645                if (label != null && !label.isEmpty()) {
646                    Object val = doc.getPropertyValue(label);
647                    if (val == null) {
648                        result.add("");
649                    } else {
650                        result.add(val.toString());
651                    }
652                } else {
653                    result.add(doc.getTitle());
654                }
655            } else {
656                result.add(messages.get("label.documentSuggestion.docNotFoundOrNotVisible") + "(" + ref + ")");
657            }
658        }
659        return result;
660    }
661
662    @SuppressWarnings("rawtypes")
663    public String resolveMultipleReferences(final Object value, final String repo, final String operationName,
664            final String idProperty, final String schemaNames) throws IOException {
665
666        if (value == null) {
667            return "[]";
668        }
669
670        List<String> storedRefs = new ArrayList<>();
671        if (value instanceof List) {
672            for (Object v : (List) value) {
673                storedRefs.add(v.toString());
674            }
675        } else if (value instanceof Object[]) {
676            for (Object v : (Object[]) value) {
677                storedRefs.add(v.toString());
678            }
679        }
680        if (storedRefs.isEmpty()) {
681            return "[]";
682        }
683
684        ByteArrayOutputStream baos = new ByteArrayOutputStream();
685        BufferedOutputStream out = new BufferedOutputStream(baos);
686        JsonGenerator jg = JsonHelper.createJsonGenerator(out);
687        jg.writeStartArray();
688
689        DocumentModelJsonWriter writer = getDocumentModelWriter(schemaNames);
690
691        for (String ref : storedRefs) {
692            DocumentModel doc = resolveReference(repo, ref, operationName, idProperty);
693            if (doc == null) {
694                processDocumentNotFound(ref, jg);
695            } else {
696                writer.write(doc, jg);
697                jg.flush();
698            }
699        }
700
701        jg.writeEndArray();
702        out.flush();
703        String json = new String(baos.toByteArray(), "UTF-8");
704
705        if (json.isEmpty()) {
706            return "[]";
707        }
708        if (json.startsWith("[") && !json.endsWith("]")) {
709            // XXX !!!
710            // AT: what's this for?
711            json = json + "]";
712        }
713
714        return json;
715    }
716
717    @SuppressWarnings("rawtypes")
718    public String resolveMultipleUserReference(final Object value, final boolean prefixed,
719            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
720            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
721            final boolean displayEmailInSuggestion, final boolean hideIcon) {
722        if (value == null) {
723            return "[]";
724        }
725        JSONArray result = new JSONArray();
726        List<String> storedRefs = new ArrayList<>();
727        if (value instanceof List) {
728            for (Object v : (List) value) {
729                storedRefs.add(v.toString());
730            }
731        } else if (value instanceof Object[]) {
732            for (Object v : (Object[]) value) {
733                storedRefs.add(v.toString());
734            }
735        }
736
737        for (String ref : storedRefs) {
738            String resolved = resolveSingleUserReference(ref, prefixed, firstLabelField, secondLabelField,
739                    thirdLabelField, hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion,
740                    hideIcon);
741            if (resolved != null && !resolved.isEmpty()) {
742                result.add(resolved);
743            }
744        }
745        return result.toString();
746    }
747
748    public List<String> resolveMultipleUserReferenceLabels(final Object value, final boolean prefixed,
749            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
750            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
751            final boolean displayEmailInSuggestion, final boolean hideIcon) {
752        return formatList(getMultipleUserReference(value, prefixed, firstLabelField, secondLabelField, thirdLabelField,
753                hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion, hideIcon));
754    }
755
756    protected DocumentModel resolveReference(final String repo, final String storedReference,
757            final String operationName, final String idProperty) {
758
759        if (storedReference == null || storedReference.isEmpty()) {
760            log.trace("No reference provided ");
761            return null;
762        }
763        DocumentModel doc = null;
764        CoreSession session;
765        try {
766            session = getRepositorySession(repo);
767            if (session == null) {
768                log.error("Unable to get CoreSession for repo " + repo);
769                return null;
770            }
771            if (operationName == null || operationName.isEmpty()) {
772                DocumentRef ref = null;
773
774                if (idProperty != null && !idProperty.isEmpty()) {
775                    String query = " select * from Document where " + idProperty + "='" + storedReference + "'";
776                    DocumentModelList docs = session.query(query);
777                    if (docs.size() > 0) {
778                        return docs.get(0);
779                    } else {
780                        log.warn("Unable to resolve doc using property " + idProperty + " and value " + storedReference);
781                        return null;
782                    }
783                } else {
784                    if (storedReference.startsWith("/")) {
785                        ref = new PathRef(storedReference);
786                    } else {
787                        ref = new IdRef(storedReference);
788                    }
789                    if (session.exists(ref)) {
790                        doc = session.getDocument(ref);
791                    } else {
792                        log.warn("Unable to resolve reference on " + ref);
793                    }
794                }
795            } else {
796                AutomationService as = Framework.getLocalService(AutomationService.class);
797                try (OperationContext ctx = new OperationContext(session)) {
798                    Map<String, Object> params = new HashMap<String, Object>();
799
800                    params.put("value", storedReference);
801                    params.put("xpath", idProperty);
802                    params.put("lang", org.jboss.seam.core.Locale.instance().getLanguage());
803                    Object result = as.run(ctx, operationName, params);
804
805                    if (result == null) {
806                        log.warn("Unable to resolve reference " + storedReference + " using property " + idProperty
807                                + " and operation" + operationName);
808                        doc = null;
809                    } else if (result instanceof DocumentModel) {
810                        doc = (DocumentModel) result;
811                    } else if (result instanceof DocumentModelList) {
812                        DocumentModelList docs = (DocumentModelList) result;
813                        if (docs.size() > 0) {
814                            doc = docs.get(0);
815                        } else {
816                            log.warn("No document found");
817                        }
818                    }
819                }
820            }
821            return doc;
822        } catch (InvalidChainException e) {
823            log.error("Unable to resolve reference", e);
824        } catch (OperationException e) {
825            log.error("Unable to resolve reference", e);
826        }
827        return doc;
828    }
829
830    protected void processDocumentNotFound(String id, JsonGenerator jg) {
831        if (StringUtils.isEmpty(id)) {
832            return;
833        }
834        try {
835            jg.writeStartObject();
836            jg.writeStringField(SuggestConstants.ID, id);
837            jg.writeStringField(Select2Common.TITLE, messages.get("label.documentSuggestion.docNotFoundOrNotVisible"));
838            jg.writeStringField(SuggestConstants.WARN_MESSAGE_LABEL, id);
839            jg.writeEndObject();
840            jg.flush();
841        } catch (IOException e) {
842            log.error("Error while writing not found message ", e);
843        }
844
845    }
846
847    public String resolveSingleDirectoryEntry(final String storedReference, final String directoryName,
848            final boolean localize, String keySeparator, final boolean dbl10n, final String labelFieldName) {
849        JSONObject result = getSingleDirectoryEntry(storedReference, directoryName, localize, keySeparator, dbl10n,
850                labelFieldName);
851        if (result != null) {
852            return result.toString();
853        } else {
854            return "";
855        }
856    }
857
858    public String resolveSingleDirectoryEntryLabel(final String storedReference, final String directoryName,
859            final boolean localize, String keySeparator, final boolean dbl10n, final String labelFieldName) {
860        JSONObject obj = getSingleDirectoryEntry(storedReference, directoryName, localize, keySeparator, dbl10n,
861                labelFieldName);
862        if (obj == null) {
863            return "";
864        }
865        return obj.optString(SuggestConstants.LABEL);
866    }
867
868    public String resolveSingleReference(final String storedReference, final String repo, final String operationName,
869            final String idProperty, final String schemaNames) throws IOException {
870
871        DocumentModel doc;
872        doc = resolveReference(repo, storedReference, operationName, idProperty);
873        ByteArrayOutputStream baos = new ByteArrayOutputStream();
874        BufferedOutputStream out = new BufferedOutputStream(baos);
875        JsonGenerator jg = JsonHelper.createJsonGenerator(out);
876        if (doc == null) {
877            processDocumentNotFound(storedReference, jg);
878        } else {
879            getDocumentModelWriter(schemaNames).write(doc, jg);
880        }
881        jg.flush();
882        return new String(baos.toByteArray(), "UTF-8");
883
884    }
885
886    public String resolveSingleReferenceLabel(final String storedReference, final String repo,
887            final String operationName, final String idProperty, final String label) {
888        DocumentModel doc = resolveReference(repo, storedReference, operationName, idProperty);
889        if (doc == null) {
890            return messages.get("label.documentSuggestion.docNotFoundOrNotVisible") + "(" + doc + ")";
891        }
892
893        if (label != null && !label.isEmpty()) {
894            Object val = doc.getPropertyValue(label);
895            if (val == null) {
896                return "";
897            } else {
898                return val.toString();
899            }
900        }
901        return doc.getTitle();
902    }
903
904    public String resolveSingleUserReference(final String storedReference, final boolean prefixed,
905            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
906            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
907            final boolean displayEmailInSuggestion, final boolean hideIcon) {
908        JSONObject result = getSingleUserReference(storedReference, prefixed, firstLabelField, secondLabelField,
909                thirdLabelField, hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion, hideIcon);
910        if (result != null) {
911            return result.toString();
912        } else {
913            return "";
914        }
915    }
916
917    public String resolveUserReferenceLabel(final String storedReference, final boolean prefixed,
918            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
919            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
920            final boolean displayEmailInSuggestion, final boolean hideIcon) {
921        JSONObject obj = getSingleUserReference(storedReference, prefixed, firstLabelField, secondLabelField,
922                thirdLabelField, hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion, hideIcon);
923        if (obj == null) {
924            return "";
925        }
926        return obj.optString(SuggestConstants.LABEL);
927    }
928
929    protected DocumentModelJsonWriter getDocumentModelWriter(final String schemaNames) {
930        MarshallerRegistry registry = Framework.getService(MarshallerRegistry.class);
931        String[] schemas = Select2Common.getSchemas(schemaNames);
932        HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance()
933                .getExternalContext()
934                .getRequest();
935        RenderingContext ctx = RenderingContextWebUtils.getBuilder(request)
936                .properties(schemas)
937                .enrichDoc("documentURL")
938                .get();
939        return registry.getInstance(ctx, DocumentModelJsonWriter.class);
940    }
941
942}