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