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