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.lang.StringUtils;
039import org.apache.commons.logging.Log;
040import org.apache.commons.logging.LogFactory;
041import org.codehaus.jackson.JsonGenerator;
042import org.jboss.seam.ScopeType;
043import org.jboss.seam.annotations.Destroy;
044import org.jboss.seam.annotations.In;
045import org.jboss.seam.annotations.Name;
046import org.jboss.seam.annotations.Scope;
047import org.jboss.seam.contexts.FacesLifecycle;
048import org.nuxeo.ecm.automation.AutomationService;
049import org.nuxeo.ecm.automation.InvalidChainException;
050import org.nuxeo.ecm.automation.OperationContext;
051import org.nuxeo.ecm.automation.OperationException;
052import org.nuxeo.ecm.automation.core.operations.services.DocumentPageProviderOperation;
053import org.nuxeo.ecm.automation.core.operations.services.directory.SuggestDirectoryEntries;
054import org.nuxeo.ecm.automation.core.operations.users.SuggestUserEntries;
055import org.nuxeo.ecm.automation.features.SuggestConstants;
056import org.nuxeo.ecm.automation.jaxrs.io.JsonHelper;
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 net.sf.json.JSONArray;
088import net.sf.json.JSONObject;
089
090/**
091 * Initialization for Select2.
092 *
093 * @since 5.7.3
094 */
095@Name("select2Actions")
096@Scope(ScopeType.EVENT)
097public class Select2ActionsBean implements Serializable {
098
099    private static final long serialVersionUID = 1L;
100
101    private static final Log log = LogFactory.getLog(Select2ActionsBean.class);
102
103    protected static final String SELECT2_RESOURCES_MARKER = "SELECT2_RESOURCES_MARKER";
104
105    private static List<String> formatList(JSONArray array) {
106        List<String> result = new ArrayList<String>();
107        if (array != null) {
108            for (int i = 0; i < array.size(); i++) {
109                result.add(array.getJSONObject(i).getString(Select2Common.LABEL));
110            }
111        }
112        return result;
113    }
114
115    @In(create = true)
116    protected Map<String, String> messages;
117
118    @In(create = true, required = false)
119    protected transient CoreSession documentManager;
120
121    protected transient CoreSession dedicatedSession = null;
122
123    @Destroy
124    public void destroy() {
125        if (dedicatedSession != null) {
126            dedicatedSession.close();
127        }
128    }
129
130    private static Map<String, String> getDefaultFormattersMap(final String suggestionFormatterName,
131            String selectionFormatterName) {
132        Map<String, String> result = new HashMap<String, String>();
133        result.put(Select2Common.SUGGESTION_FORMATTER, suggestionFormatterName);
134        result.put(Select2Common.SELECTION_FORMATTER, selectionFormatterName);
135        return result;
136    }
137
138    /**
139     * @since 5.9.3
140     */
141    protected static Map<String, String> getContextParameter(final DocumentModel doc) {
142        DocumentIdCodec documentIdCodec = new DocumentIdCodec();
143        Map<String, String> contextParameters = new HashMap<String, String>();
144        contextParameters.put("documentURL", documentIdCodec.getUrlFromDocumentView(new DocumentViewImpl(doc)));
145        return contextParameters;
146    }
147
148    public String encodeParametersForUserSuggestion(final Widget widget,
149            final Map<String, Serializable> resolvedWidgetProperties) {
150        Map<String, String> params = getDefaultFormattersMap(Select2Common.USER_DEFAULT_SUGGESTION_FORMATTER,
151                Select2Common.USER_DEFAULT_SELECTION_FORMATTER);
152        params.put(Select2Common.OPERATION_ID, SuggestUserEntries.ID);
153        return encodeParameters(widget, params, resolvedWidgetProperties);
154    }
155
156    public String encodeParametersForDirectory(final Widget widget,
157            final Map<String, Serializable> resolvedWidgetProperties) {
158        Map<String, String> params = getDefaultFormattersMap(Select2Common.DIR_DEFAULT_SUGGESTION_FORMATTER,
159                Select2Common.DIR_DEFAULT_SELECTION_FORMATTER);
160        params.put(Select2Common.OPERATION_ID, SuggestDirectoryEntries.ID);
161        return encodeParameters(widget, params, resolvedWidgetProperties);
162    }
163
164    public String encodeParameters(final Widget widget) {
165        return encodeParameters(widget, null);
166    }
167
168    public String encodeParameters(final Widget widget, final Map<String, Serializable> resolvedWidgetProperties) {
169        Map<String, String> params = getDefaultFormattersMap(Select2Common.DOC_DEFAULT_SUGGESTION_FORMATTER,
170                Select2Common.DOC_DEFAULT_SELECTION_FORMATTER);
171        params.put(Select2Common.OPERATION_ID, DocumentPageProviderOperation.ID);
172        return encodeParameters(widget, params, resolvedWidgetProperties);
173    }
174
175    /**
176     * Encode widget properties and parameters that Select2 pick them up in a hidden input.
177     *
178     * @param widget the widget
179     * @return encoded
180     * @since 5.7.3
181     */
182    public String encodeParameters(final Widget widget, final Map<String, String> defaultParams,
183            final Map<String, Serializable> resolvedWidgetProperties) {
184        ByteArrayOutputStream baos = new ByteArrayOutputStream();
185        BufferedOutputStream out = new BufferedOutputStream(baos);
186
187        JsonGenerator jg;
188        try {
189            jg = JsonHelper.createJsonGenerator(out);
190
191            jg.writeStartObject();
192
193            // multiple is not in properties and we must add it because Select2
194            // needs to know.
195            jg.writeStringField("multiple", "" + isMultiSelection(widget));
196
197            final boolean isTranslated = widget.isTranslated();
198
199            // Are we writing or reading
200            boolean readonly = !widget.getMode().equals("edit") && !widget.getMode().equals("create");
201            jg.writeStringField(Select2Common.READ_ONLY_PARAM, Boolean.toString(readonly));
202
203            Map<String, Serializable> propertySet = null;
204            if (resolvedWidgetProperties != null) {
205                propertySet = resolvedWidgetProperties;
206            } else {
207                propertySet = widget.getProperties();
208            }
209
210            boolean hasPlaceholder = false;
211            boolean hasAjaxReRender = false;
212            boolean hasWidth = false;
213            boolean hasMinChars = false;
214
215            for (Entry<String, Serializable> entry : propertySet.entrySet()) {
216
217                if (entry.getValue() == null) {
218                    continue;
219                }
220
221                if (defaultParams != null) {
222                    // Widget properties have priority on default properties
223                    defaultParams.remove(entry.getKey());
224                }
225
226                String value = entry.getValue().toString();
227
228                if (entry.getKey().equals(Select2Common.PLACEHOLDER)) {
229                    hasPlaceholder = true;
230                    // placeholder can be translated
231                    if (isTranslated || Boolean.parseBoolean((String) propertySet.get("translatePlaceholder"))) {
232                        value = messages.get(entry.getValue().toString());
233                    }
234                } else if (entry.getKey().equals(Select2Common.AJAX_RERENDER)) {
235                    hasAjaxReRender = true;
236                } else if (entry.getKey().equals(Select2Common.WIDTH)) {
237                    hasWidth = true;
238                } else if (entry.getKey().equals(Select2Common.MIN_CHARS)) {
239                    hasMinChars = true;
240                }
241
242                jg.writeStringField(entry.getKey(), value);
243
244            }
245
246            if (defaultParams != null) {
247                // All default params which are not in widget properties
248                for (Entry<String, String> e : defaultParams.entrySet()) {
249                    jg.writeStringField(e.getKey(), e.getValue());
250                }
251            }
252
253            if (!hasPlaceholder) {
254                // No placeholder provider and Select2 requires one to enable
255                // the
256                // reset button.
257                jg.writeStringField(Select2Common.PLACEHOLDER, messages.get("label.vocabulary.selectValue"));
258            }
259
260            if (!hasWidth) {
261                jg.writeStringField(Select2Common.WIDTH, Select2Common.DEFAULT_WIDTH);
262            }
263
264            if (!hasMinChars) {
265                jg.writeNumberField(Select2Common.MIN_CHARS, Select2Common.DEFAULT_MIN_CHARS);
266            }
267
268            if (hasAjaxReRender) {
269                jg.writeStringField(Select2Common.RERENDER_JS_FUNCTION_NAME, widget.getId() + "_reRender");
270            }
271
272            jg.writeEndObject();
273            jg.flush();
274            out.flush();
275            return new String(baos.toByteArray(), "UTF-8");
276        } catch (IOException e) {
277            log.error("Could not encode parameters", e);
278            return null;
279        }
280    }
281
282    protected LayoutStore getLayoutStore() {
283        return Framework.getService(LayoutStore.class);
284    }
285
286    @SuppressWarnings("rawtypes")
287    protected JSONArray getMultipleDirectoryEntries(final Object value, final String directoryName,
288            final boolean localize, String keySeparator, final boolean dbl10n, final String labelFieldName) {
289        JSONArray result = new JSONArray();
290        if (value == null) {
291            return result;
292        }
293
294        List<String> storedRefs = new ArrayList<>();
295        if (value instanceof List) {
296            for (Object v : (List) value) {
297                storedRefs.add(v.toString());
298            }
299        } else if (value instanceof Object[]) {
300            for (Object v : (Object[]) value) {
301                storedRefs.add(v.toString());
302            }
303        }
304
305        DirectoryService directoryService = Framework.getLocalService(DirectoryService.class);
306        try {
307            Directory directory = directoryService.getDirectory(directoryName);
308            if (directory == null) {
309                log.error("Could not find directory with name " + directoryName);
310                return null;
311            }
312
313            try (Session session = directory.getSession()) {
314                String schemaName = directory.getSchema();
315                SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class);
316                Schema schema = schemaManager.getSchema(schemaName);
317                final Locale locale = org.jboss.seam.core.Locale.instance();
318                final String label = SuggestConstants.getLabelFieldName(schema, dbl10n, labelFieldName,
319                        locale.getLanguage());
320
321                for (String ref : storedRefs) {
322                    JSONObject obj = resolveDirectoryEntry(ref, keySeparator, session, schema, label, localize, dbl10n);
323                    if (obj != null) {
324                        result.add(obj);
325                    }
326                }
327                return result;
328            }
329        } catch (DirectoryException de) {
330            log.error("An error occured while obtaining directory " + directoryName, de);
331            return result;
332        }
333    }
334
335    @SuppressWarnings("rawtypes")
336    protected JSONArray getMultipleUserReference(final Object value, final boolean prefixed,
337            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
338            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
339            final boolean displayEmailInSuggestion, final boolean hideIcon) {
340        if (value == null) {
341            return null;
342        }
343        JSONArray result = new JSONArray();
344        List<String> storedRefs = new ArrayList<>();
345        if (value instanceof List) {
346            for (Object v : (List) value) {
347                storedRefs.add(v.toString());
348            }
349        } else if (value instanceof Object[]) {
350            for (Object v : (Object[]) value) {
351                storedRefs.add(v.toString());
352            }
353        }
354
355        for (String ref : storedRefs) {
356            JSONObject resolved = getSingleUserReference(ref, prefixed, firstLabelField, secondLabelField,
357                    thirdLabelField, hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion,
358                    hideIcon);
359            if (resolved != null && !resolved.isEmpty()) {
360                result.add(resolved);
361            }
362        }
363        return result;
364    }
365
366    protected CoreSession getRepositorySession(String repoName) {
367
368        if (repoName == null || repoName.isEmpty()) {
369            RepositoryManager rm = Framework.getLocalService(RepositoryManager.class);
370            repoName = rm.getDefaultRepositoryName();
371        }
372
373        if (documentManager != null && documentManager.getRepositoryName().equals(repoName)) {
374            return documentManager;
375        }
376
377        dedicatedSession = CoreInstance.openCoreSession(repoName);
378        return dedicatedSession;
379    }
380
381    protected JSONObject getSingleDirectoryEntry(final String storedReference, final String directoryName,
382            final boolean localize, String keySeparator, final boolean dbl10n, final String labelFieldName) {
383
384        if (storedReference == null || storedReference.isEmpty()) {
385            return null;
386        }
387
388        DirectoryService directoryService = Framework.getLocalService(DirectoryService.class);
389        try {
390            Directory directory = directoryService.getDirectory(directoryName);
391            if (directory == null) {
392                log.error("Could not find directory with name " + directoryName);
393                return null;
394            }
395
396            try (Session session = directory.getSession()) {
397                String schemaName = directory.getSchema();
398                SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class);
399                Schema schema = schemaManager.getSchema(schemaName);
400
401                final Locale locale = org.jboss.seam.core.Locale.instance();
402                final String label = SuggestConstants.getLabelFieldName(schema, dbl10n, labelFieldName,
403                        locale.getLanguage());
404
405                JSONObject obj = resolveDirectoryEntry(storedReference, keySeparator, session, schema, label, localize,
406                        dbl10n);
407
408                return obj;
409            }
410        } catch (DirectoryException de) {
411            log.error("An error occured while obtaining directory " + directoryName, de);
412            return null;
413        }
414    }
415
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.getLocalService(UserManager.class);
421        SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class);
422        DirectoryService dirService = Framework.getLocalService(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    }
618
619    @SuppressWarnings("rawtypes")
620    public List<String> resolveMultipleReferenceLabels(final Object value, final String repo,
621            final String operationName, final String idProperty, final String label) {
622
623        List<String> result = new ArrayList<>();
624
625        if (value == null) {
626            return result;
627        }
628
629        List<String> storedRefs = new ArrayList<>();
630        if (value instanceof List) {
631            for (Object v : (List) value) {
632                storedRefs.add(v.toString());
633            }
634        } else if (value instanceof Object[]) {
635            for (Object v : (Object[]) value) {
636                storedRefs.add(v.toString());
637            }
638        }
639
640        for (String ref : storedRefs) {
641            DocumentModel doc = resolveReference(repo, ref, operationName, idProperty);
642            if (doc != null) {
643                if (label != null && !label.isEmpty()) {
644                    Object val = doc.getPropertyValue(label);
645                    if (val == null) {
646                        result.add("");
647                    } else {
648                        result.add(val.toString());
649                    }
650                } else {
651                    result.add(doc.getTitle());
652                }
653            } else {
654                result.add(messages.get("label.documentSuggestion.docNotFoundOrNotVisible") + "(" + ref + ")");
655            }
656        }
657        return result;
658    }
659
660    @SuppressWarnings("rawtypes")
661    public String resolveMultipleReferences(final Object value, final String repo, final String operationName,
662            final String idProperty, final String schemaNames) throws IOException {
663
664        if (value == null) {
665            return "[]";
666        }
667
668        List<String> storedRefs = new ArrayList<>();
669        if (value instanceof List) {
670            for (Object v : (List) value) {
671                storedRefs.add(v.toString());
672            }
673        } else if (value instanceof Object[]) {
674            for (Object v : (Object[]) value) {
675                storedRefs.add(v.toString());
676            }
677        }
678        if (storedRefs.isEmpty()) {
679            return "[]";
680        }
681
682        ByteArrayOutputStream baos = new ByteArrayOutputStream();
683        BufferedOutputStream out = new BufferedOutputStream(baos);
684        JsonGenerator jg = JsonHelper.createJsonGenerator(out);
685        jg.writeStartArray();
686
687        DocumentModelJsonWriter writer = getDocumentModelWriter(schemaNames);
688
689        for (String ref : storedRefs) {
690            DocumentModel doc = resolveReference(repo, ref, operationName, idProperty);
691            if (doc == null) {
692                processDocumentNotFound(ref, jg);
693            } else {
694                writer.write(doc, jg);
695                jg.flush();
696            }
697        }
698
699        jg.writeEndArray();
700        out.flush();
701        String json = new String(baos.toByteArray(), "UTF-8");
702
703        if (json.isEmpty()) {
704            return "[]";
705        }
706        if (json.startsWith("[") && !json.endsWith("]")) {
707            // XXX !!!
708            // AT: what's this for?
709            json = json + "]";
710        }
711
712        return json;
713    }
714
715    @SuppressWarnings("rawtypes")
716    public String resolveMultipleUserReference(final Object value, final boolean prefixed, final String firstLabelField,
717            final String secondLabelField, final String thirdLabelField, final boolean hideFirstLabel,
718            final boolean hideSecondLabel, final boolean hideThirdLabel, final boolean displayEmailInSuggestion,
719            final boolean hideIcon) {
720        if (value == null) {
721            return "[]";
722        }
723        JSONArray result = new JSONArray();
724        List<String> storedRefs = new ArrayList<>();
725        if (value instanceof List) {
726            for (Object v : (List) value) {
727                storedRefs.add(v.toString());
728            }
729        } else if (value instanceof Object[]) {
730            for (Object v : (Object[]) value) {
731                storedRefs.add(v.toString());
732            }
733        }
734
735        for (String ref : storedRefs) {
736            String resolved = resolveSingleUserReference(ref, prefixed, firstLabelField, secondLabelField,
737                    thirdLabelField, hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion,
738                    hideIcon);
739            if (resolved != null && !resolved.isEmpty()) {
740                result.add(resolved);
741            }
742        }
743        return result.toString();
744    }
745
746    public List<String> resolveMultipleUserReferenceLabels(final Object value, final boolean prefixed,
747            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
748            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
749            final boolean displayEmailInSuggestion, final boolean hideIcon) {
750        return formatList(getMultipleUserReference(value, prefixed, firstLabelField, secondLabelField, thirdLabelField,
751                hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion, hideIcon));
752    }
753
754    protected DocumentModel resolveReference(final String repo, final String storedReference,
755            final String operationName, final String idProperty) {
756
757        if (storedReference == null || storedReference.isEmpty()) {
758            log.trace("No reference provided ");
759            return null;
760        }
761        DocumentModel doc = null;
762        CoreSession session;
763        try {
764            session = getRepositorySession(repo);
765            if (session == null) {
766                log.error("Unable to get CoreSession for repo " + repo);
767                return null;
768            }
769            if (operationName == null || operationName.isEmpty()) {
770                doc = Select2Common.resolveReference(idProperty, storedReference, session);
771            } else {
772                AutomationService as = Framework.getLocalService(AutomationService.class);
773                try (OperationContext ctx = new OperationContext(session)) {
774                    Map<String, Object> params = new HashMap<String, Object>();
775
776                    params.put("value", storedReference);
777                    params.put("xpath", idProperty);
778                    params.put("lang", org.jboss.seam.core.Locale.instance().getLanguage());
779                    Object result = as.run(ctx, operationName, params);
780
781                    if (result == null) {
782                        log.warn("Unable to resolve reference " + storedReference + " using property " + idProperty
783                                + " and operation" + operationName);
784                        doc = null;
785                    } else if (result instanceof DocumentModel) {
786                        doc = (DocumentModel) result;
787                    } else if (result instanceof DocumentModelList) {
788                        DocumentModelList docs = (DocumentModelList) result;
789                        if (docs.size() > 0) {
790                            doc = docs.get(0);
791                        } else {
792                            log.warn("No document found");
793                        }
794                    }
795                }
796            }
797            return doc;
798        } catch (InvalidChainException e) {
799            log.error("Unable to resolve reference", e);
800        } catch (OperationException e) {
801            log.error("Unable to resolve reference", e);
802        }
803        return doc;
804    }
805
806    protected void processDocumentNotFound(String id, JsonGenerator jg) {
807        if (StringUtils.isEmpty(id)) {
808            return;
809        }
810        try {
811            jg.writeStartObject();
812            jg.writeStringField(SuggestConstants.ID, id);
813            jg.writeStringField(Select2Common.TITLE, messages.get("label.documentSuggestion.docNotFoundOrNotVisible"));
814            jg.writeStringField(SuggestConstants.WARN_MESSAGE_LABEL, id);
815            jg.writeEndObject();
816            jg.flush();
817        } catch (IOException e) {
818            log.error("Error while writing not found message ", e);
819        }
820
821    }
822
823    public String resolveSingleDirectoryEntry(final String storedReference, final String directoryName,
824            final boolean localize, String keySeparator, final boolean dbl10n, final String labelFieldName) {
825        JSONObject result = getSingleDirectoryEntry(storedReference, directoryName, localize, keySeparator, dbl10n,
826                labelFieldName);
827        if (result != null) {
828            return result.toString();
829        } else {
830            return "";
831        }
832    }
833
834    public String resolveSingleDirectoryEntryLabel(final String storedReference, final String directoryName,
835            final boolean localize, String keySeparator, final boolean dbl10n, final String labelFieldName) {
836        JSONObject obj = getSingleDirectoryEntry(storedReference, directoryName, localize, keySeparator, dbl10n,
837                labelFieldName);
838        if (obj == null) {
839            return "";
840        }
841        return obj.optString(SuggestConstants.LABEL);
842    }
843
844    public String resolveSingleReference(final String storedReference, final String repo, final String operationName,
845            final String idProperty, final String schemaNames) throws IOException {
846
847        DocumentModel doc;
848        doc = resolveReference(repo, storedReference, operationName, idProperty);
849        ByteArrayOutputStream baos = new ByteArrayOutputStream();
850        BufferedOutputStream out = new BufferedOutputStream(baos);
851        JsonGenerator jg = JsonHelper.createJsonGenerator(out);
852        if (doc == null) {
853            processDocumentNotFound(storedReference, jg);
854        } else {
855            getDocumentModelWriter(schemaNames).write(doc, jg);
856        }
857        jg.flush();
858        return new String(baos.toByteArray(), "UTF-8");
859
860    }
861
862    public String resolveSingleReferenceLabel(final String storedReference, final String repo,
863            final String operationName, final String idProperty, final String label) {
864        DocumentModel doc = resolveReference(repo, storedReference, operationName, idProperty);
865        if (doc == null) {
866            return messages.get("label.documentSuggestion.docNotFoundOrNotVisible") + "(" + doc + ")";
867        }
868
869        if (label != null && !label.isEmpty()) {
870            Object val = doc.getPropertyValue(label);
871            if (val == null) {
872                return "";
873            } else {
874                return val.toString();
875            }
876        }
877        return doc.getTitle();
878    }
879
880    public String resolveSingleUserReference(final String storedReference, final boolean prefixed,
881            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
882            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
883            final boolean displayEmailInSuggestion, final boolean hideIcon) {
884        JSONObject result = getSingleUserReference(storedReference, prefixed, firstLabelField, secondLabelField,
885                thirdLabelField, hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion, hideIcon);
886        if (result != null) {
887            return result.toString();
888        } else {
889            return "";
890        }
891    }
892
893    public String resolveUserReferenceLabel(final String storedReference, final boolean prefixed,
894            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
895            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
896            final boolean displayEmailInSuggestion, final boolean hideIcon) {
897        JSONObject obj = getSingleUserReference(storedReference, prefixed, firstLabelField, secondLabelField,
898                thirdLabelField, hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion, hideIcon);
899        if (obj == null) {
900            return "";
901        }
902        return obj.optString(SuggestConstants.LABEL);
903    }
904
905    protected DocumentModelJsonWriter getDocumentModelWriter(final String schemaNames) {
906        MarshallerRegistry registry = Framework.getService(MarshallerRegistry.class);
907        String[] schemas = Select2Common.getSchemas(schemaNames);
908        HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance()
909                                                                      .getExternalContext()
910                                                                      .getRequest();
911        RenderingContext ctx = RenderingContextWebUtils.getBuilder(request)
912                                                       .properties(schemas)
913                                                       .enrichDoc("documentURL")
914                                                       .get();
915        return registry.getInstance(ctx, DocumentModelJsonWriter.class);
916    }
917
918}