001/*
002 * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     <a href="mailto:tdelprat@nuxeo.com">Tiry</a>
016 *     <a href="mailto:grenard@nuxeo.com">Guillaume</a>
017 */
018
019package org.nuxeo.ecm.platform.ui.select2;
020
021import java.io.BufferedOutputStream;
022import java.io.IOException;
023import java.io.Serializable;
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Locale;
028import java.util.Map;
029import java.util.Map.Entry;
030
031import javax.faces.context.FacesContext;
032import javax.faces.event.PhaseId;
033import javax.servlet.http.HttpServletRequest;
034
035import net.sf.json.JSONArray;
036import net.sf.json.JSONObject;
037
038import org.apache.commons.io.output.ByteArrayOutputStream;
039import org.apache.commons.lang.StringUtils;
040import org.apache.commons.logging.Log;
041import org.apache.commons.logging.LogFactory;
042import org.codehaus.jackson.JsonGenerator;
043import org.jboss.seam.ScopeType;
044import org.jboss.seam.annotations.Destroy;
045import org.jboss.seam.annotations.In;
046import org.jboss.seam.annotations.Name;
047import org.jboss.seam.annotations.Scope;
048import org.jboss.seam.contexts.FacesLifecycle;
049import org.nuxeo.ecm.automation.AutomationService;
050import org.nuxeo.ecm.automation.InvalidChainException;
051import org.nuxeo.ecm.automation.OperationContext;
052import org.nuxeo.ecm.automation.OperationException;
053import org.nuxeo.ecm.automation.core.operations.services.DocumentPageProviderOperation;
054import org.nuxeo.ecm.automation.jaxrs.io.JsonHelper;
055import org.nuxeo.ecm.core.api.CoreInstance;
056import org.nuxeo.ecm.core.api.CoreSession;
057import org.nuxeo.ecm.core.api.DocumentModel;
058import org.nuxeo.ecm.core.api.DocumentModelList;
059import org.nuxeo.ecm.core.api.DocumentRef;
060import org.nuxeo.ecm.core.api.IdRef;
061import org.nuxeo.ecm.core.api.NuxeoGroup;
062import org.nuxeo.ecm.core.api.NuxeoPrincipal;
063import org.nuxeo.ecm.core.api.PathRef;
064import org.nuxeo.ecm.core.api.PropertyException;
065import org.nuxeo.ecm.core.api.repository.RepositoryManager;
066import org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonWriter;
067import org.nuxeo.ecm.core.io.registry.MarshallerRegistry;
068import org.nuxeo.ecm.core.io.registry.context.RenderingContext;
069import org.nuxeo.ecm.core.schema.SchemaManager;
070import org.nuxeo.ecm.core.schema.types.Field;
071import org.nuxeo.ecm.core.schema.types.QName;
072import org.nuxeo.ecm.core.schema.types.Schema;
073import org.nuxeo.ecm.directory.Directory;
074import org.nuxeo.ecm.directory.DirectoryException;
075import org.nuxeo.ecm.directory.Session;
076import org.nuxeo.ecm.directory.api.DirectoryService;
077import org.nuxeo.ecm.platform.forms.layout.api.Widget;
078import org.nuxeo.ecm.platform.forms.layout.api.WidgetTypeConfiguration;
079import org.nuxeo.ecm.platform.forms.layout.api.WidgetTypeDefinition;
080import org.nuxeo.ecm.platform.forms.layout.api.service.LayoutStore;
081import org.nuxeo.ecm.platform.ui.select2.automation.SuggestDirectoryEntries;
082import org.nuxeo.ecm.platform.ui.select2.automation.SuggestUserEntries;
083import org.nuxeo.ecm.platform.ui.select2.common.Select2Common;
084import org.nuxeo.ecm.platform.url.DocumentViewImpl;
085import org.nuxeo.ecm.platform.url.codec.DocumentIdCodec;
086import org.nuxeo.ecm.platform.usermanager.UserManager;
087import org.nuxeo.ecm.webengine.jaxrs.coreiodelegate.RenderingContextWebUtils;
088import org.nuxeo.runtime.api.Framework;
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 = Select2Common.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 = Select2Common.getLabelFieldName(schema, dbl10n, labelFieldName, 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    protected JSONObject getSingleUserReference(final String storedReference, final boolean prefixed,
416            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
417            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
418            final boolean displayEmailInSuggestion, final boolean hideIcon) {
419        UserManager userManager = Framework.getLocalService(UserManager.class);
420        SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class);
421        DirectoryService dirService = Framework.getLocalService(DirectoryService.class);
422        JSONObject obj = new JSONObject();
423        if (storedReference == null || storedReference.isEmpty()) {
424            return null;
425        }
426        DocumentModel user = null;
427        DocumentModel group = null;
428        Directory userDir = dirService.getDirectory(userManager.getUserDirectoryName());
429        if (prefixed) {
430            if (storedReference.startsWith(NuxeoPrincipal.PREFIX)) {
431                user = userManager.getUserModel(storedReference.substring(NuxeoPrincipal.PREFIX.length()));
432            } else if (storedReference.startsWith(NuxeoGroup.PREFIX)) {
433                group = userManager.getGroupModel(storedReference.substring(NuxeoGroup.PREFIX.length()));
434            } else {
435                log.warn("User reference is prefixed but prefix was not found on reference: " + storedReference);
436                return createNotFoundEntry(storedReference);
437            }
438        } else {
439            user = userManager.getUserModel(storedReference);
440            if (user == null) {
441                group = userManager.getGroupModel(storedReference);
442            }
443        }
444        if (user != null) {
445            Schema schema = schemaManager.getSchema(userManager.getUserSchemaName());
446            for (Field field : schema.getFields()) {
447                QName fieldName = field.getName();
448                String key = fieldName.getLocalName();
449                Serializable value = user.getPropertyValue(fieldName.getPrefixedName());
450                if (key.equals(userDir.getPasswordField())) {
451                    continue;
452                }
453                obj.element(key, value);
454            }
455            String userId = user.getId();
456            obj.put(Select2Common.ID, userId);
457            obj.put(Select2Common.TYPE_KEY_NAME, Select2Common.USER_TYPE);
458            obj.put(Select2Common.PREFIXED_ID_KEY_NAME, NuxeoPrincipal.PREFIX + userId);
459            Select2Common.computeUserLabel(obj, firstLabelField, secondLabelField, thirdLabelField, hideFirstLabel,
460                    hideSecondLabel, hideThirdLabel, displayEmailInSuggestion, userId);
461            Select2Common.computeUserGroupIcon(obj, hideIcon);
462        } else if (group != null) {
463            Schema schema = schemaManager.getSchema(userManager.getGroupSchemaName());
464            for (Field field : schema.getFields()) {
465                QName fieldName = field.getName();
466                String key = fieldName.getLocalName();
467                Serializable value = group.getPropertyValue(fieldName.getPrefixedName());
468                obj.element(key, value);
469            }
470            // If the group hasn't an label, let's put the groupid
471            String groupId = group.getId();
472            Select2Common.computeGroupLabel(obj, groupId, userManager.getGroupLabelField(), hideFirstLabel);
473            obj.put(Select2Common.ID, groupId);
474            obj.put(Select2Common.TYPE_KEY_NAME, Select2Common.GROUP_TYPE);
475            obj.put(Select2Common.PREFIXED_ID_KEY_NAME, NuxeoGroup.PREFIX + groupId);
476            Select2Common.computeUserGroupIcon(obj, hideIcon);
477        } else {
478            log.warn("Could not resolve user or group reference: " + storedReference);
479            return createNotFoundEntry(storedReference);
480        }
481        return obj;
482    }
483
484    public boolean isMultiSelection(final Widget widget) {
485        String wtCat = widget.getTypeCategory();
486        if (StringUtils.isBlank(wtCat)) {
487            wtCat = "jsf";
488        }
489        WidgetTypeDefinition wtDef = getLayoutStore().getWidgetTypeDefinition(wtCat, widget.getType());
490        if (wtDef != null) {
491            WidgetTypeConfiguration conf = wtDef.getConfiguration();
492            if (conf != null) {
493                return conf.isList();
494            }
495        }
496        return false;
497    }
498
499    public boolean mustIncludeResources() {
500        FacesContext facesContext = FacesContext.getCurrentInstance();
501        if (facesContext != null) {
502            PhaseId currentPhaseId = FacesLifecycle.getPhaseId();
503            if (currentPhaseId.equals(PhaseId.RENDER_RESPONSE)) {
504                HttpServletRequest request = (HttpServletRequest) facesContext.getExternalContext().getRequest();
505
506                if (request.getAttribute(SELECT2_RESOURCES_MARKER) != null) {
507                    return false;
508                } else {
509                    request.setAttribute(SELECT2_RESOURCES_MARKER, "done");
510                    return true;
511                }
512            }
513        }
514        return false;
515    }
516
517    protected JSONObject createNotFoundEntry(final String id) {
518        return createEntryWithWarnMessage(id, "entry not found");
519    }
520
521    protected JSONObject createEntryWithWarnMessage(final String label, final String warnMessage) {
522        JSONObject obj = new JSONObject();
523        obj.put(Select2Common.ID, label);
524        obj.put(Select2Common.ABSOLUTE_LABEL, label);
525        obj.put(Select2Common.LABEL, label);
526        obj.put(Select2Common.WARN_MESSAGE_LABEL, warnMessage);
527        return obj;
528    }
529
530    protected JSONObject resolveDirectoryEntry(final String storedReference, String keySeparator,
531            final Session session, final Schema schema, final String label, final boolean localize, final boolean dbl10n) {
532        if (storedReference == null || storedReference.isEmpty()) {
533            log.trace("No reference provided ");
534            return null;
535        }
536
537        if (keySeparator == null || keySeparator.isEmpty()) {
538            keySeparator = Select2Common.DEFAULT_KEY_SEPARATOR;
539        }
540
541        String entryId = storedReference.substring(storedReference.lastIndexOf(keySeparator) + 1,
542                storedReference.length());
543
544        DocumentModel result = session.getEntry(entryId);
545        if (result == null) {
546            log.warn("Unable to resolve entry " + storedReference);
547            return createNotFoundEntry(storedReference);
548        }
549
550        JSONObject obj = new JSONObject();
551        for (Field field : schema.getFields()) {
552            QName fieldName = field.getName();
553            String key = fieldName.getLocalName();
554            Serializable value = result.getPropertyValue(fieldName.getPrefixedName());
555            if (label.equals(key)) {
556                if (localize && !dbl10n) {
557                    value = messages.get(value);
558                }
559                obj.element(Select2Common.LABEL, value);
560                obj.element(Select2Common.ABSOLUTE_LABEL,
561                        getParentAbsoluteLabel(storedReference, keySeparator, session, fieldName, localize, dbl10n));
562            } else {
563                obj.element(key, value);
564            }
565        }
566
567        // Add a warning message if the entity is obsolete
568        if (obj.containsKey(Select2Common.OBSOLETE_FIELD_ID) && obj.getInt(Select2Common.OBSOLETE_FIELD_ID) > 0) {
569            obj.element(Select2Common.WARN_MESSAGE_LABEL, messages.get("label.vocabulary.entry.obsolete"));
570        }
571
572        obj.element(Select2Common.COMPUTED_ID, storedReference);
573        return obj;
574    }
575
576    /**
577     * @since 5.9.3
578     */
579    protected String getParentAbsoluteLabel(final String entryId, final String keySeparator, final Session session,
580            final QName labelFieldName, final boolean localize, final boolean dbl10n) throws PropertyException {
581        String[] split = entryId.split(keySeparator);
582        String result = "";
583        for (int i = 0; i < split.length; i++) {
584            DocumentModel entry = session.getEntry(split[i]);
585            if (entry != null) {
586                Serializable value = entry.getPropertyValue(labelFieldName.getPrefixedName());
587                if (localize && !dbl10n) {
588                    value = messages.get(value);
589                }
590                result += (i > 0 ? "/" : "") + value;
591            }
592        }
593
594        return result;
595    }
596
597    public String resolveMultipleDirectoryEntries(final Object value, final String directoryName,
598            final boolean localize, String keySeparator, final boolean dbl10n, final String labelFieldName) {
599        JSONArray result = getMultipleDirectoryEntries(value, directoryName, localize, keySeparator, dbl10n,
600                labelFieldName);
601        if (result != null) {
602            return result.toString();
603        } else {
604            return "[]";
605        }
606    }
607
608    public List<String> resolveMultipleDirectoryEntryLabels(final Object value, final String directoryName,
609            final boolean localize, final String keySeparator, final boolean dbl10n, final String labelFieldName) {
610        return formatList(getMultipleDirectoryEntries(value, directoryName, localize, keySeparator, dbl10n,
611                labelFieldName));
612    }
613
614    @SuppressWarnings("rawtypes")
615    public List<String> resolveMultipleReferenceLabels(final Object value, final String repo,
616            final String operationName, final String idProperty, final String label) {
617
618        List<String> result = new ArrayList<>();
619
620        if (value == null) {
621            return result;
622        }
623
624        List<String> storedRefs = new ArrayList<>();
625        if (value instanceof List) {
626            for (Object v : (List) value) {
627                storedRefs.add(v.toString());
628            }
629        } else if (value instanceof Object[]) {
630            for (Object v : (Object[]) value) {
631                storedRefs.add(v.toString());
632            }
633        }
634
635        for (String ref : storedRefs) {
636            DocumentModel doc = resolveReference(repo, ref, operationName, idProperty);
637            if (doc != null) {
638                if (label != null && !label.isEmpty()) {
639                    Object val = doc.getPropertyValue(label);
640                    if (val == null) {
641                        result.add("");
642                    } else {
643                        result.add(val.toString());
644                    }
645                } else {
646                    result.add(doc.getTitle());
647                }
648            } else {
649                result.add(messages.get("label.documentSuggestion.docNotFoundOrNotVisible") + "(" + ref + ")");
650            }
651        }
652        return result;
653    }
654
655    @SuppressWarnings("rawtypes")
656    public String resolveMultipleReferences(final Object value, final String repo, final String operationName,
657            final String idProperty, final String schemaNames) throws IOException {
658
659        if (value == null) {
660            return "[]";
661        }
662
663        List<String> storedRefs = new ArrayList<>();
664        if (value instanceof List) {
665            for (Object v : (List) value) {
666                storedRefs.add(v.toString());
667            }
668        } else if (value instanceof Object[]) {
669            for (Object v : (Object[]) value) {
670                storedRefs.add(v.toString());
671            }
672        }
673        if (storedRefs.isEmpty()) {
674            return "[]";
675        }
676
677        ByteArrayOutputStream baos = new ByteArrayOutputStream();
678        BufferedOutputStream out = new BufferedOutputStream(baos);
679        JsonGenerator jg = JsonHelper.createJsonGenerator(out);
680        jg.writeStartArray();
681
682        DocumentModelJsonWriter writer = getDocumentModelWriter(schemaNames);
683
684        for (String ref : storedRefs) {
685            DocumentModel doc = resolveReference(repo, ref, operationName, idProperty);
686            if (doc == null) {
687                processDocumentNotFound(ref, jg);
688            } else {
689                writer.write(doc, jg);
690                jg.flush();
691            }
692        }
693
694        jg.writeEndArray();
695        out.flush();
696        String json = new String(baos.toByteArray(), "UTF-8");
697
698        if (json.isEmpty()) {
699            return "[]";
700        }
701        if (json.startsWith("[") && !json.endsWith("]")) {
702            // XXX !!!
703            // AT: what's this for?
704            json = json + "]";
705        }
706
707        return json;
708    }
709
710    @SuppressWarnings("rawtypes")
711    public String resolveMultipleUserReference(final Object value, final boolean prefixed,
712            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
713            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
714            final boolean displayEmailInSuggestion, final boolean hideIcon) {
715        if (value == null) {
716            return "[]";
717        }
718        JSONArray result = new JSONArray();
719        List<String> storedRefs = new ArrayList<>();
720        if (value instanceof List) {
721            for (Object v : (List) value) {
722                storedRefs.add(v.toString());
723            }
724        } else if (value instanceof Object[]) {
725            for (Object v : (Object[]) value) {
726                storedRefs.add(v.toString());
727            }
728        }
729
730        for (String ref : storedRefs) {
731            String resolved = resolveSingleUserReference(ref, prefixed, firstLabelField, secondLabelField,
732                    thirdLabelField, hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion,
733                    hideIcon);
734            if (resolved != null && !resolved.isEmpty()) {
735                result.add(resolved);
736            }
737        }
738        return result.toString();
739    }
740
741    public List<String> resolveMultipleUserReferenceLabels(final Object value, final boolean prefixed,
742            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
743            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
744            final boolean displayEmailInSuggestion, final boolean hideIcon) {
745        return formatList(getMultipleUserReference(value, prefixed, firstLabelField, secondLabelField, thirdLabelField,
746                hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion, hideIcon));
747    }
748
749    protected DocumentModel resolveReference(final String repo, final String storedReference,
750            final String operationName, final String idProperty) {
751
752        if (storedReference == null || storedReference.isEmpty()) {
753            log.trace("No reference provided ");
754            return null;
755        }
756        DocumentModel doc = null;
757        CoreSession session;
758        try {
759            session = getRepositorySession(repo);
760            if (session == null) {
761                log.error("Unable to get CoreSession for repo " + repo);
762                return null;
763            }
764            if (operationName == null || operationName.isEmpty()) {
765                DocumentRef ref = null;
766
767                if (idProperty != null && !idProperty.isEmpty()) {
768                    String query = " select * from Document where " + idProperty + "='" + storedReference + "'";
769                    DocumentModelList docs = session.query(query);
770                    if (docs.size() > 0) {
771                        return docs.get(0);
772                    } else {
773                        log.warn("Unable to resolve doc using property " + idProperty + " and value " + storedReference);
774                        return null;
775                    }
776                } else {
777                    if (storedReference.startsWith("/")) {
778                        ref = new PathRef(storedReference);
779                    } else {
780                        ref = new IdRef(storedReference);
781                    }
782                    if (session.exists(ref)) {
783                        doc = session.getDocument(ref);
784                    } else {
785                        log.warn("Unable to resolve reference on " + ref);
786                    }
787                }
788            } else {
789                AutomationService as = Framework.getLocalService(AutomationService.class);
790                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            return doc;
814        } catch (InvalidChainException e) {
815            log.error("Unable to resolve reference", e);
816        } catch (OperationException e) {
817            log.error("Unable to resolve reference", e);
818        }
819        return doc;
820    }
821
822    protected void processDocumentNotFound(String id, JsonGenerator jg) {
823        if (StringUtils.isEmpty(id)) {
824            return;
825        }
826        try {
827            jg.writeStartObject();
828            jg.writeStringField(Select2Common.ID, id);
829            jg.writeStringField(Select2Common.TITLE, messages.get("label.documentSuggestion.docNotFoundOrNotVisible"));
830            jg.writeStringField(Select2Common.WARN_MESSAGE_LABEL, id);
831            jg.writeEndObject();
832            jg.flush();
833        } catch (IOException e) {
834            log.error("Error while writing not found message ", e);
835        }
836
837    }
838
839    public String resolveSingleDirectoryEntry(final String storedReference, final String directoryName,
840            final boolean localize, String keySeparator, final boolean dbl10n, final String labelFieldName) {
841        JSONObject result = getSingleDirectoryEntry(storedReference, directoryName, localize, keySeparator, dbl10n,
842                labelFieldName);
843        if (result != null) {
844            return result.toString();
845        } else {
846            return "";
847        }
848    }
849
850    public String resolveSingleDirectoryEntryLabel(final String storedReference, final String directoryName,
851            final boolean localize, String keySeparator, final boolean dbl10n, final String labelFieldName) {
852        JSONObject obj = getSingleDirectoryEntry(storedReference, directoryName, localize, keySeparator, dbl10n,
853                labelFieldName);
854        if (obj == null) {
855            return "";
856        }
857        return obj.optString(Select2Common.LABEL);
858    }
859
860    public String resolveSingleReference(final String storedReference, final String repo, final String operationName,
861            final String idProperty, final String schemaNames) throws IOException {
862
863        DocumentModel doc;
864        doc = resolveReference(repo, storedReference, operationName, idProperty);
865        ByteArrayOutputStream baos = new ByteArrayOutputStream();
866        BufferedOutputStream out = new BufferedOutputStream(baos);
867        JsonGenerator jg = JsonHelper.createJsonGenerator(out);
868        if (doc == null) {
869            processDocumentNotFound(storedReference, jg);
870        } else {
871            getDocumentModelWriter(schemaNames).write(doc, jg);
872        }
873        jg.flush();
874        return new String(baos.toByteArray(), "UTF-8");
875
876    }
877
878    public String resolveSingleReferenceLabel(final String storedReference, final String repo,
879            final String operationName, final String idProperty, final String label) {
880        DocumentModel doc = resolveReference(repo, storedReference, operationName, idProperty);
881        if (doc == null) {
882            return messages.get("label.documentSuggestion.docNotFoundOrNotVisible") + "(" + doc + ")";
883        }
884
885        if (label != null && !label.isEmpty()) {
886            Object val = doc.getPropertyValue(label);
887            if (val == null) {
888                return "";
889            } else {
890                return val.toString();
891            }
892        }
893        return doc.getTitle();
894    }
895
896    public String resolveSingleUserReference(final String storedReference, final boolean prefixed,
897            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
898            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
899            final boolean displayEmailInSuggestion, final boolean hideIcon) {
900        JSONObject result = getSingleUserReference(storedReference, prefixed, firstLabelField, secondLabelField,
901                thirdLabelField, hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion, hideIcon);
902        if (result != null) {
903            return result.toString();
904        } else {
905            return "";
906        }
907    }
908
909    public String resolveUserReferenceLabel(final String storedReference, final boolean prefixed,
910            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
911            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
912            final boolean displayEmailInSuggestion, final boolean hideIcon) {
913        JSONObject obj = getSingleUserReference(storedReference, prefixed, firstLabelField, secondLabelField,
914                thirdLabelField, hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion, hideIcon);
915        if (obj == null) {
916            return "";
917        }
918        return obj.optString(Select2Common.LABEL);
919    }
920
921    protected DocumentModelJsonWriter getDocumentModelWriter(final String schemaNames) {
922        MarshallerRegistry registry = Framework.getService(MarshallerRegistry.class);
923        String[] schemas = Select2Common.getSchemas(schemaNames);
924        HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance()
925                .getExternalContext()
926                .getRequest();
927        RenderingContext ctx = RenderingContextWebUtils.getBuilder(request)
928                .properties(schemas)
929                .enrichDoc("documentURL")
930                .get();
931        return registry.getInstance(ctx, DocumentModelJsonWriter.class);
932    }
933
934}