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}