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