001/*
002 * (C) Copyright 2018 Nuxeo (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 *     pierre
018 */
019package org.nuxeo.ecm.platform.csv.export.io;
020
021import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON;
022import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE;
023
024import java.io.IOException;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.List;
028import java.util.Locale;
029import java.util.stream.Collectors;
030
031import org.apache.commons.csv.CSVPrinter;
032import org.nuxeo.common.utils.i18n.I18NUtils;
033import org.nuxeo.ecm.core.api.DocumentModel;
034import org.nuxeo.ecm.core.api.model.Property;
035import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
036import org.nuxeo.ecm.core.api.model.impl.ArrayProperty;
037import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty.ScalarMemberProperty;
038import org.nuxeo.ecm.core.io.marshallers.csv.AbstractCSVWriter;
039import org.nuxeo.ecm.core.io.registry.reflect.Setup;
040import org.nuxeo.ecm.core.schema.types.ListType;
041import org.nuxeo.ecm.core.schema.types.Type;
042import org.nuxeo.ecm.core.schema.types.primitives.BinaryType;
043import org.nuxeo.ecm.directory.Directory;
044import org.nuxeo.ecm.directory.Session;
045
046/**
047 * @since 10.3
048 */
049@Setup(mode = SINGLETON, priority = REFERENCE)
050public class DocumentPropertyCSVWriter extends AbstractCSVWriter<Property> {
051
052    public static final String LIST_DELIMITER = " | ";
053
054    public static final String LANG_CTX_DATA = "lang";
055
056    public static final String UNKNOWN_TRANSLATED_VALUE_LABEL = "unknown translated value";
057
058    public DocumentPropertyCSVWriter() {
059        super();
060    }
061
062    @Override
063    protected void write(Property property, CSVPrinter printer) throws IOException {
064        if (property == null) {
065            printer.print(null);
066        } else if (property.isScalar()) {
067            if (property instanceof ScalarMemberProperty && property.getParent().getValue() == null) {
068                printer.print(null);
069                return;
070            }
071            writeScalarProperty(property, printer);
072        } else if (property.isList()) {
073            writeListProperty(property, printer);
074        } else {
075            writeUnsupported(property.getType(), printer);
076        }
077    }
078
079    @Override
080    protected void writeHeader(Property property, CSVPrinter printer) throws IOException {
081        printer.printRecord(property.getXPath());
082    }
083
084    protected void writeScalarProperty(Property property, CSVPrinter printer) throws IOException {
085        Object value = property.getValue();
086        Type type = property.getType();
087        if (type instanceof BinaryType) {
088            writeUnsupported(type, printer);
089        } else {
090            String valueAsString = null;
091            if (value == null) {
092                printer.print(null);
093            } else {
094                valueAsString = type.encode(value);
095                printer.print(valueAsString);
096            }
097            Directory vocabulary = DocumentModelCSVHelper.getVocabulary(type);
098            if (vocabulary != null) {
099                writeScalarVocabularyProperty(valueAsString, vocabulary, printer);
100            }
101        }
102    }
103
104    protected void writeScalarVocabularyProperty(String value, Directory vocabulary, CSVPrinter printer)
105            throws IOException {
106        if (value == null) {
107            printer.print(null);
108        } else {
109            try (Session session = vocabulary.getSession()) {
110                String lang = ctx.getParameter(LANG_CTX_DATA);
111                printer.print(getTranslatedValue(session, vocabulary.getSchema(), lang, value));
112            }
113        }
114    }
115
116    protected void writeListProperty(Property property, CSVPrinter printer) throws IOException {
117        ListType type = (ListType) property.getType();
118        if (property instanceof ArrayProperty) {
119            Object[] array = (Object[]) property.getValue();
120            Type itemType = type.getFieldType();
121            Directory vocabulary = DocumentModelCSVHelper.getVocabulary(itemType);
122            if (array == null) {
123                printer.print(null);
124                if (vocabulary != null) {
125                    printer.print(null);
126                }
127                return;
128            }
129            if (itemType instanceof BinaryType) {
130                writeUnsupported(type, printer);
131            } else {
132                String value = Arrays.stream(array).map(itemType::encode).collect(Collectors.joining(LIST_DELIMITER));
133                printer.print(value);
134                if (vocabulary != null) {
135                    String[] values = Arrays.stream(array).map(itemType::encode).toArray(String[]::new);
136                    writeListVocabularyProperty(values, vocabulary, printer);
137                }
138            }
139        } else {
140            writeUnsupported(type, printer);
141        }
142    }
143
144    protected void writeListVocabularyProperty(String[] values, Directory vocabulary, CSVPrinter printer)
145            throws IOException {
146        List<String> translated = new ArrayList<>();
147        try (Session session = vocabulary.getSession()) {
148            String lang = ctx.getParameter(LANG_CTX_DATA);
149            for (String value : values) {
150                translated.add(getTranslatedValue(session, vocabulary.getSchema(), lang, value));
151            }
152        }
153        printer.print(translated.stream().collect(Collectors.joining(LIST_DELIMITER)));
154    }
155
156    protected String getTranslatedValue(Session session, String schema, String lang, String value) {
157        if (value.contains("/")) {
158            value = value.substring(value.lastIndexOf("/") + 1);
159        }
160        DocumentModel entry = session.getEntry(value);
161        if (entry == null) {
162            return UNKNOWN_TRANSLATED_VALUE_LABEL;
163        }
164
165        String label;
166        try {
167            label = (String) entry.getProperty(schema, "label");
168        } catch (PropertyNotFoundException e) {
169            try {
170                // Check if it comes from a l10nxvocabulary, and return this value if it exists,
171                // or else return the id
172                return (String) entry.getProperty(schema, "label_en");
173            } catch (PropertyNotFoundException e1) {
174                return value;
175            }
176        }
177        Locale locale = lang != null ? Locale.forLanguageTag(lang) : ctx.getLocale();
178        return I18NUtils.getMessageString("messages", label, new Object[0], locale);
179    }
180
181    protected void writeUnsupported(Type type, CSVPrinter printer) throws IOException {
182        printer.print(String.format("type %s is not supported", type.getName()));
183    }
184
185}