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