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    /**
500     * @deprecated since 7.10: JSF resources mechanism allows to detect resources already included in the page natively.
501     */
502    public boolean mustIncludeResources() {
503        FacesContext facesContext = FacesContext.getCurrentInstance();
504        if (facesContext != null) {
505            PhaseId currentPhaseId = FacesLifecycle.getPhaseId();
506            if (currentPhaseId.equals(PhaseId.RENDER_RESPONSE)) {
507                HttpServletRequest request = (HttpServletRequest) facesContext.getExternalContext().getRequest();
508
509                if (request.getAttribute(SELECT2_RESOURCES_MARKER) != null) {
510                    return false;
511                } else {
512                    request.setAttribute(SELECT2_RESOURCES_MARKER, "done");
513                    return true;
514                }
515            }
516        }
517        return false;
518    }
519
520    protected JSONObject createNotFoundEntry(final String id) {
521        return createEntryWithWarnMessage(id, "entry not found");
522    }
523
524    protected JSONObject createEntryWithWarnMessage(final String label, final String warnMessage) {
525        JSONObject obj = new JSONObject();
526        obj.put(Select2Common.ID, label);
527        obj.put(Select2Common.ABSOLUTE_LABEL, label);
528        obj.put(Select2Common.LABEL, label);
529        obj.put(Select2Common.WARN_MESSAGE_LABEL, warnMessage);
530        return obj;
531    }
532
533    protected JSONObject resolveDirectoryEntry(final String storedReference, String keySeparator,
534            final Session session, final Schema schema, final String label, final boolean localize, final boolean dbl10n) {
535        if (storedReference == null || storedReference.isEmpty()) {
536            log.trace("No reference provided ");
537            return null;
538        }
539
540        if (keySeparator == null || keySeparator.isEmpty()) {
541            keySeparator = Select2Common.DEFAULT_KEY_SEPARATOR;
542        }
543
544        String entryId = storedReference.substring(storedReference.lastIndexOf(keySeparator) + 1,
545                storedReference.length());
546
547        DocumentModel result = session.getEntry(entryId);
548        if (result == null) {
549            log.warn("Unable to resolve entry " + storedReference);
550            return createNotFoundEntry(storedReference);
551        }
552
553        JSONObject obj = new JSONObject();
554        for (Field field : schema.getFields()) {
555            QName fieldName = field.getName();
556            String key = fieldName.getLocalName();
557            Serializable value = result.getPropertyValue(fieldName.getPrefixedName());
558            if (label.equals(key)) {
559                if (localize && !dbl10n) {
560                    value = messages.get(value);
561                }
562                obj.element(Select2Common.LABEL, value);
563                obj.element(Select2Common.ABSOLUTE_LABEL,
564                        getParentAbsoluteLabel(storedReference, keySeparator, session, fieldName, localize, dbl10n));
565            } else {
566                obj.element(key, value);
567            }
568        }
569
570        // Add a warning message if the entity is obsolete
571        if (obj.containsKey(Select2Common.OBSOLETE_FIELD_ID) && obj.getInt(Select2Common.OBSOLETE_FIELD_ID) > 0) {
572            obj.element(Select2Common.WARN_MESSAGE_LABEL, messages.get("label.vocabulary.entry.obsolete"));
573        }
574
575        obj.element(Select2Common.COMPUTED_ID, storedReference);
576        return obj;
577    }
578
579    /**
580     * @since 5.9.3
581     */
582    protected String getParentAbsoluteLabel(final String entryId, final String keySeparator, final Session session,
583            final QName labelFieldName, final boolean localize, final boolean dbl10n) throws PropertyException {
584        String[] split = entryId.split(keySeparator);
585        String result = "";
586        for (int i = 0; i < split.length; i++) {
587            DocumentModel entry = session.getEntry(split[i]);
588            if (entry != null) {
589                Serializable value = entry.getPropertyValue(labelFieldName.getPrefixedName());
590                if (localize && !dbl10n) {
591                    value = messages.get(value);
592                }
593                result += (i > 0 ? "/" : "") + value;
594            }
595        }
596
597        return result;
598    }
599
600    public String resolveMultipleDirectoryEntries(final Object value, final String directoryName,
601            final boolean localize, String keySeparator, final boolean dbl10n, final String labelFieldName) {
602        JSONArray result = getMultipleDirectoryEntries(value, directoryName, localize, keySeparator, dbl10n,
603                labelFieldName);
604        if (result != null) {
605            return result.toString();
606        } else {
607            return "[]";
608        }
609    }
610
611    public List<String> resolveMultipleDirectoryEntryLabels(final Object value, final String directoryName,
612            final boolean localize, final String keySeparator, final boolean dbl10n, final String labelFieldName) {
613        return formatList(getMultipleDirectoryEntries(value, directoryName, localize, keySeparator, dbl10n,
614                labelFieldName));
615    }
616
617    @SuppressWarnings("rawtypes")
618    public List<String> resolveMultipleReferenceLabels(final Object value, final String repo,
619            final String operationName, final String idProperty, final String label) {
620
621        List<String> result = new ArrayList<>();
622
623        if (value == null) {
624            return result;
625        }
626
627        List<String> storedRefs = new ArrayList<>();
628        if (value instanceof List) {
629            for (Object v : (List) value) {
630                storedRefs.add(v.toString());
631            }
632        } else if (value instanceof Object[]) {
633            for (Object v : (Object[]) value) {
634                storedRefs.add(v.toString());
635            }
636        }
637
638        for (String ref : storedRefs) {
639            DocumentModel doc = resolveReference(repo, ref, operationName, idProperty);
640            if (doc != null) {
641                if (label != null && !label.isEmpty()) {
642                    Object val = doc.getPropertyValue(label);
643                    if (val == null) {
644                        result.add("");
645                    } else {
646                        result.add(val.toString());
647                    }
648                } else {
649                    result.add(doc.getTitle());
650                }
651            } else {
652                result.add(messages.get("label.documentSuggestion.docNotFoundOrNotVisible") + "(" + ref + ")");
653            }
654        }
655        return result;
656    }
657
658    @SuppressWarnings("rawtypes")
659    public String resolveMultipleReferences(final Object value, final String repo, final String operationName,
660            final String idProperty, final String schemaNames) throws IOException {
661
662        if (value == null) {
663            return "[]";
664        }
665
666        List<String> storedRefs = new ArrayList<>();
667        if (value instanceof List) {
668            for (Object v : (List) value) {
669                storedRefs.add(v.toString());
670            }
671        } else if (value instanceof Object[]) {
672            for (Object v : (Object[]) value) {
673                storedRefs.add(v.toString());
674            }
675        }
676        if (storedRefs.isEmpty()) {
677            return "[]";
678        }
679
680        ByteArrayOutputStream baos = new ByteArrayOutputStream();
681        BufferedOutputStream out = new BufferedOutputStream(baos);
682        JsonGenerator jg = JsonHelper.createJsonGenerator(out);
683        jg.writeStartArray();
684
685        DocumentModelJsonWriter writer = getDocumentModelWriter(schemaNames);
686
687        for (String ref : storedRefs) {
688            DocumentModel doc = resolveReference(repo, ref, operationName, idProperty);
689            if (doc == null) {
690                processDocumentNotFound(ref, jg);
691            } else {
692                writer.write(doc, jg);
693                jg.flush();
694            }
695        }
696
697        jg.writeEndArray();
698        out.flush();
699        String json = new String(baos.toByteArray(), "UTF-8");
700
701        if (json.isEmpty()) {
702            return "[]";
703        }
704        if (json.startsWith("[") && !json.endsWith("]")) {
705            // XXX !!!
706            // AT: what's this for?
707            json = json + "]";
708        }
709
710        return json;
711    }
712
713    @SuppressWarnings("rawtypes")
714    public String resolveMultipleUserReference(final Object value, final boolean prefixed,
715            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
716            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
717            final boolean displayEmailInSuggestion, final boolean hideIcon) {
718        if (value == null) {
719            return "[]";
720        }
721        JSONArray result = new JSONArray();
722        List<String> storedRefs = new ArrayList<>();
723        if (value instanceof List) {
724            for (Object v : (List) value) {
725                storedRefs.add(v.toString());
726            }
727        } else if (value instanceof Object[]) {
728            for (Object v : (Object[]) value) {
729                storedRefs.add(v.toString());
730            }
731        }
732
733        for (String ref : storedRefs) {
734            String resolved = resolveSingleUserReference(ref, prefixed, firstLabelField, secondLabelField,
735                    thirdLabelField, hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion,
736                    hideIcon);
737            if (resolved != null && !resolved.isEmpty()) {
738                result.add(resolved);
739            }
740        }
741        return result.toString();
742    }
743
744    public List<String> resolveMultipleUserReferenceLabels(final Object value, final boolean prefixed,
745            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
746            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
747            final boolean displayEmailInSuggestion, final boolean hideIcon) {
748        return formatList(getMultipleUserReference(value, prefixed, firstLabelField, secondLabelField, thirdLabelField,
749                hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion, hideIcon));
750    }
751
752    protected DocumentModel resolveReference(final String repo, final String storedReference,
753            final String operationName, final String idProperty) {
754
755        if (storedReference == null || storedReference.isEmpty()) {
756            log.trace("No reference provided ");
757            return null;
758        }
759        DocumentModel doc = null;
760        CoreSession session;
761        try {
762            session = getRepositorySession(repo);
763            if (session == null) {
764                log.error("Unable to get CoreSession for repo " + repo);
765                return null;
766            }
767            if (operationName == null || operationName.isEmpty()) {
768                DocumentRef ref = null;
769
770                if (idProperty != null && !idProperty.isEmpty()) {
771                    String query = " select * from Document where " + idProperty + "='" + storedReference + "'";
772                    DocumentModelList docs = session.query(query);
773                    if (docs.size() > 0) {
774                        return docs.get(0);
775                    } else {
776                        log.warn("Unable to resolve doc using property " + idProperty + " and value " + storedReference);
777                        return null;
778                    }
779                } else {
780                    if (storedReference.startsWith("/")) {
781                        ref = new PathRef(storedReference);
782                    } else {
783                        ref = new IdRef(storedReference);
784                    }
785                    if (session.exists(ref)) {
786                        doc = session.getDocument(ref);
787                    } else {
788                        log.warn("Unable to resolve reference on " + ref);
789                    }
790                }
791            } else {
792                AutomationService as = Framework.getLocalService(AutomationService.class);
793                OperationContext ctx = new OperationContext(session);
794                Map<String, Object> params = new HashMap<String, Object>();
795
796                params.put("value", storedReference);
797                params.put("xpath", idProperty);
798                params.put("lang", org.jboss.seam.core.Locale.instance().getLanguage());
799                Object result = as.run(ctx, operationName, params);
800
801                if (result == null) {
802                    log.warn("Unable to resolve reference " + storedReference + " using property " + idProperty
803                            + " and operation" + operationName);
804                    doc = null;
805                } else if (result instanceof DocumentModel) {
806                    doc = (DocumentModel) result;
807                } else if (result instanceof DocumentModelList) {
808                    DocumentModelList docs = (DocumentModelList) result;
809                    if (docs.size() > 0) {
810                        doc = docs.get(0);
811                    } else {
812                        log.warn("No document found");
813                    }
814                }
815            }
816            return doc;
817        } catch (InvalidChainException e) {
818            log.error("Unable to resolve reference", e);
819        } catch (OperationException e) {
820            log.error("Unable to resolve reference", e);
821        }
822        return doc;
823    }
824
825    protected void processDocumentNotFound(String id, JsonGenerator jg) {
826        if (StringUtils.isEmpty(id)) {
827            return;
828        }
829        try {
830            jg.writeStartObject();
831            jg.writeStringField(Select2Common.ID, id);
832            jg.writeStringField(Select2Common.TITLE, messages.get("label.documentSuggestion.docNotFoundOrNotVisible"));
833            jg.writeStringField(Select2Common.WARN_MESSAGE_LABEL, id);
834            jg.writeEndObject();
835            jg.flush();
836        } catch (IOException e) {
837            log.error("Error while writing not found message ", e);
838        }
839
840    }
841
842    public String resolveSingleDirectoryEntry(final String storedReference, final String directoryName,
843            final boolean localize, String keySeparator, final boolean dbl10n, final String labelFieldName) {
844        JSONObject result = getSingleDirectoryEntry(storedReference, directoryName, localize, keySeparator, dbl10n,
845                labelFieldName);
846        if (result != null) {
847            return result.toString();
848        } else {
849            return "";
850        }
851    }
852
853    public String resolveSingleDirectoryEntryLabel(final String storedReference, final String directoryName,
854            final boolean localize, String keySeparator, final boolean dbl10n, final String labelFieldName) {
855        JSONObject obj = getSingleDirectoryEntry(storedReference, directoryName, localize, keySeparator, dbl10n,
856                labelFieldName);
857        if (obj == null) {
858            return "";
859        }
860        return obj.optString(Select2Common.LABEL);
861    }
862
863    public String resolveSingleReference(final String storedReference, final String repo, final String operationName,
864            final String idProperty, final String schemaNames) throws IOException {
865
866        DocumentModel doc;
867        doc = resolveReference(repo, storedReference, operationName, idProperty);
868        ByteArrayOutputStream baos = new ByteArrayOutputStream();
869        BufferedOutputStream out = new BufferedOutputStream(baos);
870        JsonGenerator jg = JsonHelper.createJsonGenerator(out);
871        if (doc == null) {
872            processDocumentNotFound(storedReference, jg);
873        } else {
874            getDocumentModelWriter(schemaNames).write(doc, jg);
875        }
876        jg.flush();
877        return new String(baos.toByteArray(), "UTF-8");
878
879    }
880
881    public String resolveSingleReferenceLabel(final String storedReference, final String repo,
882            final String operationName, final String idProperty, final String label) {
883        DocumentModel doc = resolveReference(repo, storedReference, operationName, idProperty);
884        if (doc == null) {
885            return messages.get("label.documentSuggestion.docNotFoundOrNotVisible") + "(" + doc + ")";
886        }
887
888        if (label != null && !label.isEmpty()) {
889            Object val = doc.getPropertyValue(label);
890            if (val == null) {
891                return "";
892            } else {
893                return val.toString();
894            }
895        }
896        return doc.getTitle();
897    }
898
899    public String resolveSingleUserReference(final String storedReference, final boolean prefixed,
900            final String firstLabelField, final String secondLabelField, final String thirdLabelField,
901            final boolean hideFirstLabel, final boolean hideSecondLabel, final boolean hideThirdLabel,
902            final boolean displayEmailInSuggestion, final boolean hideIcon) {
903        JSONObject result = getSingleUserReference(storedReference, prefixed, firstLabelField, secondLabelField,
904                thirdLabelField, hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion, hideIcon);
905        if (result != null) {
906            return result.toString();
907        } else {
908            return "";
909        }
910    }
911
912    public String resolveUserReferenceLabel(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 obj = getSingleUserReference(storedReference, prefixed, firstLabelField, secondLabelField,
917                thirdLabelField, hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion, hideIcon);
918        if (obj == null) {
919            return "";
920        }
921        return obj.optString(Select2Common.LABEL);
922    }
923
924    protected DocumentModelJsonWriter getDocumentModelWriter(final String schemaNames) {
925        MarshallerRegistry registry = Framework.getService(MarshallerRegistry.class);
926        String[] schemas = Select2Common.getSchemas(schemaNames);
927        HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance()
928                .getExternalContext()
929                .getRequest();
930        RenderingContext ctx = RenderingContextWebUtils.getBuilder(request)
931                .properties(schemas)
932                .enrichDoc("documentURL")
933                .get();
934        return registry.getInstance(ctx, DocumentModelJsonWriter.class);
935    }
936
937}