001/*
002 * (C) Copyright 2015-2019 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 *     Nicolas Chapurlat <nchapurlat@nuxeo.com>
018 */
019
020package org.nuxeo.ecm.directory.io;
021
022import static java.util.Locale.ENGLISH;
023import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
024import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.MAX_DEPTH_PARAM;
025import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON;
026import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE;
027
028import java.io.Closeable;
029import java.io.IOException;
030import java.util.Locale;
031import java.util.MissingResourceException;
032import java.util.Set;
033
034import javax.inject.Inject;
035
036import org.apache.commons.lang3.StringUtils;
037import org.apache.commons.logging.Log;
038import org.apache.commons.logging.LogFactory;
039import org.nuxeo.common.utils.i18n.I18NUtils;
040import org.nuxeo.ecm.core.api.DocumentModel;
041import org.nuxeo.ecm.core.api.model.Property;
042import org.nuxeo.ecm.core.io.marshallers.json.ExtensibleEntityJsonWriter;
043import org.nuxeo.ecm.core.io.marshallers.json.OutputStreamWithJsonWriter;
044import org.nuxeo.ecm.core.io.marshallers.json.document.DocumentPropertiesJsonReader;
045import org.nuxeo.ecm.core.io.marshallers.json.enrichers.AbstractJsonEnricher;
046import org.nuxeo.ecm.core.io.registry.Writer;
047import org.nuxeo.ecm.core.io.registry.reflect.Setup;
048import org.nuxeo.ecm.core.schema.SchemaManager;
049import org.nuxeo.ecm.core.schema.types.Field;
050import org.nuxeo.ecm.core.schema.types.QName;
051import org.nuxeo.ecm.core.schema.types.Schema;
052import org.nuxeo.ecm.directory.Directory;
053import org.nuxeo.ecm.directory.Session;
054import org.nuxeo.ecm.directory.api.DirectoryEntry;
055import org.nuxeo.ecm.directory.api.DirectoryService;
056
057import com.fasterxml.jackson.core.JsonGenerator;
058
059/**
060 * Convert {@link DirectoryEntry} to Json.
061 * <p>
062 * This marshaller is enrichable: register class implementing {@link AbstractJsonEnricher} and managing
063 * {@link DirectoryEntry}.
064 * </p>
065 * <p>
066 * This marshaller is also extensible: extend it and simply override
067 * {@link ExtensibleEntityJsonWriter#extend(Object, JsonGenerator)}.
068 * </p>
069 * <p>
070 * Format is:
071 *
072 * <pre>
073 * {@code
074 * {
075 *   "entity-type": "directoryEntry",
076 *   "directoryName": "DIRECTORY_NAME", <- use it to update an existing document
077 *   "properties": {
078 *     <- entry properties depending on the directory schema (password fields are hidden)
079 *     <- format is managed by {@link DocumentPropertiesJsonReader}
080 *   }
081 *             <-- contextParameters if there are enrichers activated
082 *             <-- additional property provided by extend() method
083 * }
084 * }
085 * </pre>
086 *
087 * @since 7.2
088 */
089@Setup(mode = SINGLETON, priority = REFERENCE)
090public class DirectoryEntryJsonWriter extends ExtensibleEntityJsonWriter<DirectoryEntry> {
091
092    public static final String ENTITY_TYPE = "directoryEntry";
093
094    /** @since 11.1 */
095    public static final String PARENT_FIELD_NAME = "parent";
096
097    private static final String MESSAGES_BUNDLE = "messages";
098
099    private static final Log log = LogFactory.getLog(DirectoryEntryJsonWriter.class);
100
101    @Inject
102    private SchemaManager schemaManager;
103
104    @Inject
105    private DirectoryService directoryService;
106
107    public DirectoryEntryJsonWriter() {
108        super(ENTITY_TYPE, DirectoryEntry.class);
109    }
110
111    @Override
112    protected void writeEntityBody(DirectoryEntry entry, JsonGenerator jg) throws IOException {
113        String directoryName = entry.getDirectoryName();
114        Directory directory = directoryService.getDirectory(directoryName);
115        String parentDirectoryName = directory.getParentDirectory();
116        boolean hasParentDirectory = StringUtils.isNotBlank(parentDirectoryName);
117        String schemaName = directory.getSchema();
118        String passwordField = directory.getPasswordField();
119        DocumentModel document = entry.getDocumentModel();
120        jg.writeStringField("directoryName", directoryName);
121        jg.writeStringField("id", document.getId());
122        Schema schema = schemaManager.getSchema(schemaName);
123        Writer<Property> propertyWriter = registry.getWriter(ctx, Property.class, APPLICATION_JSON_TYPE);
124        // for each properties, fetch it
125        jg.writeObjectFieldStart("properties");
126        Set<String> translated = ctx.getTranslated(ENTITY_TYPE);
127        Set<String> fetched = ctx.getFetched(ENTITY_TYPE);
128        for (Field field : schema.getFields()) {
129            QName fieldName = field.getName();
130            String key = fieldName.getLocalName();
131            jg.writeFieldName(key);
132            if (key.equals(passwordField)) {
133                jg.writeString("");
134            } else {
135                Property property = document.getProperty(fieldName.getPrefixedName());
136                boolean managed = false;
137                Object value = property.getValue();
138                if (value instanceof String && StringUtils.isNotEmpty((String) value)) {
139                    String valueString = (String) value;
140                    String localName = fieldName.getLocalName();
141                    if (fetched.contains(localName)) {
142                        // try to fetch a referenced entry (parent for example)
143                        try (Closeable resource = ctx.wrap().with(MAX_DEPTH_PARAM, "max").open()) {
144                            String dName = PARENT_FIELD_NAME.equals(localName) && hasParentDirectory
145                                    ? parentDirectoryName
146                                    : directoryName;
147                            managed = writeFetchedValue(jg, dName, localName, valueString);
148                        }
149                    } else if (translated.contains(localName)) {
150                        // try to fetch a translated property
151                        managed = writeTranslatedValue(jg, localName, valueString);
152                    }
153                }
154                if (!managed) {
155                    propertyWriter.write(property, Property.class, Property.class, APPLICATION_JSON_TYPE,
156                            new OutputStreamWithJsonWriter(jg));
157                }
158            }
159        }
160        jg.writeEndObject();
161    }
162
163    protected boolean writeFetchedValue(JsonGenerator jg, String directoryName, String fieldName, String value)
164            throws IOException {
165        try (Session session = directoryService.open(directoryName)) {
166            DocumentModel entryModel = session.getEntry(value);
167            if (entryModel != null) {
168                DirectoryEntry entry = new DirectoryEntry(directoryName, entryModel);
169                writeEntity(entry, jg);
170                return true;
171            }
172        }
173        return false;
174    }
175
176    protected boolean writeTranslatedValue(JsonGenerator jg, String fieldName, String value) throws IOException {
177        Locale locale = ctx.getLocale();
178        String msg = getMessageString(value, new Object[0], locale);
179        if (msg == null && locale != ENGLISH) {
180            msg = getMessageString(value, new Object[0], ENGLISH);
181        }
182        if (msg != null && !msg.equals(value)) {
183            jg.writeString(msg);
184            return true;
185        }
186        return false;
187    }
188
189    public static String getMessageString(String key, Object[] params, Locale locale) {
190        try {
191            return I18NUtils.getMessageString(MESSAGES_BUNDLE, key, params, locale);
192        } catch (MissingResourceException e) {
193            log.trace("No bundle found", e);
194            return null;
195        }
196    }
197
198}