001/*
002 * (C) Copyright 2015 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 *     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.Session;
053import org.nuxeo.ecm.directory.api.DirectoryEntry;
054import org.nuxeo.ecm.directory.api.DirectoryService;
055
056import com.fasterxml.jackson.core.JsonGenerator;
057import com.thoughtworks.xstream.io.json.JsonWriter;
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(DirectoryEntry, JsonWriter)}.
068 * </p>
069 * <p>
070 * Format is:
071 *
072 * <pre>
073 * {
074 *   "entity-type": "directoryEntry",
075 *   "directoryName": "DIRECTORY_NAME", <- use it to update an existing document
076 *   "properties": {
077 *     <- entry properties depending on the directory schema (password fields are hidden)
078 *     <- format is managed by {@link DocumentPropertiesJsonReader}
079 *   }
080 *             <-- contextParameters if there are enrichers activated
081 *             <-- additional property provided by extend() method
082 * }
083 * </pre>
084 * </p>
085 *
086 * @since 7.2
087 */
088@Setup(mode = SINGLETON, priority = REFERENCE)
089public class DirectoryEntryJsonWriter extends ExtensibleEntityJsonWriter<DirectoryEntry> {
090
091    public static final String ENTITY_TYPE = "directoryEntry";
092
093    private static final String MESSAGES_BUNDLE = "messages";
094
095    private static final Log log = LogFactory.getLog(DirectoryEntryJsonWriter.class);
096
097    @Inject
098    private SchemaManager schemaManager;
099
100    @Inject
101    private DirectoryService directoryService;
102
103    public DirectoryEntryJsonWriter() {
104        super(ENTITY_TYPE, DirectoryEntry.class);
105    }
106
107    @Override
108    protected void writeEntityBody(DirectoryEntry entry, JsonGenerator jg) throws IOException {
109        String directoryName = entry.getDirectoryName();
110        DocumentModel document = entry.getDocumentModel();
111        String schemaName = directoryService.getDirectorySchema(directoryName);
112        String passwordField = directoryService.getDirectoryPasswordField(directoryName);
113        jg.writeStringField("directoryName", directoryName);
114        jg.writeStringField("id", document.getId());
115        Schema schema = schemaManager.getSchema(schemaName);
116        Writer<Property> propertyWriter = registry.getWriter(ctx, Property.class, APPLICATION_JSON_TYPE);
117        // for each properties, fetch it
118        jg.writeObjectFieldStart("properties");
119        Set<String> translated = ctx.getTranslated(ENTITY_TYPE);
120        Set<String> fetched = ctx.getFetched(ENTITY_TYPE);
121        for (Field field : schema.getFields()) {
122            QName fieldName = field.getName();
123            String key = fieldName.getLocalName();
124            jg.writeFieldName(key);
125            if (key.equals(passwordField)) {
126                jg.writeString("");
127            } else {
128                Property property = document.getProperty(fieldName.getPrefixedName());
129                boolean managed = false;
130                Object value = property.getValue();
131                if (value instanceof String && StringUtils.isNotEmpty((String) value)) {
132                    String valueString = (String) value;
133                    if (fetched.contains(fieldName.getLocalName())) {
134                        // try to fetch a referenced entry (parent for example)
135                        try (Closeable resource = ctx.wrap().with(MAX_DEPTH_PARAM, "max").open()) {
136                            managed = writeFetchedValue(jg, directoryName, fieldName.getLocalName(), valueString);
137                        }
138                    } else if (translated.contains(fieldName.getLocalName())) {
139                        // try to fetch a translated property
140                        managed = writeTranslatedValue(jg, fieldName.getLocalName(), valueString);
141                    }
142                }
143                if (!managed) {
144                    propertyWriter.write(property, Property.class, Property.class, APPLICATION_JSON_TYPE,
145                            new OutputStreamWithJsonWriter(jg));
146                }
147            }
148        }
149        jg.writeEndObject();
150    }
151
152    protected boolean writeFetchedValue(JsonGenerator jg, String directoryName, String fieldName, String value)
153            throws IOException {
154        try (Session session = directoryService.open(directoryName)) {
155            DocumentModel entryModel = session.getEntry(value);
156            if (entryModel != null) {
157                DirectoryEntry entry = new DirectoryEntry(directoryName, entryModel);
158                writeEntity(entry, jg);
159                return true;
160            }
161        }
162        return false;
163    }
164
165    protected boolean writeTranslatedValue(JsonGenerator jg, String fieldName, String value) throws IOException {
166        Locale locale = ctx.getLocale();
167        String msg = getMessageString(value, new Object[0], locale);
168        if (msg == null && locale != ENGLISH) {
169            msg = getMessageString(value, new Object[0], ENGLISH);
170        }
171        if (msg != null && !msg.equals(value)) {
172            jg.writeString(msg);
173            return true;
174        }
175        return false;
176    }
177
178    public static String getMessageString(String key, Object[] params, Locale locale) {
179        try {
180            return I18NUtils.getMessageString(MESSAGES_BUNDLE, key, params, locale);
181        } catch (MissingResourceException e) {
182            log.trace("No bundle found", e);
183            return null;
184        }
185    }
186
187}