001/* 002 * (C) Copyright 2015-2020 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 org.nuxeo.ecm.core.io.marshallers.json.document.DocumentPropertiesJsonReader.DEFAULT_SCHEMA_NAME; 023import static org.nuxeo.ecm.core.io.marshallers.json.document.DocumentPropertiesJsonReader.FALLBACK_RESOLVER; 024import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON; 025import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE; 026import static org.nuxeo.ecm.directory.DirectoryEntryResolver.PARAM_DIRECTORY; 027import static org.nuxeo.ecm.directory.io.DirectoryEntryJsonWriter.ENTITY_TYPE; 028 029import java.io.Closeable; 030import java.io.IOException; 031import java.lang.reflect.ParameterizedType; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035 036import javax.inject.Inject; 037 038import org.apache.commons.lang3.StringUtils; 039import org.apache.commons.lang3.reflect.TypeUtils; 040import org.nuxeo.ecm.core.api.DocumentModel; 041import org.nuxeo.ecm.core.api.model.Property; 042import org.nuxeo.ecm.core.io.marshallers.json.EntityJsonReader; 043import org.nuxeo.ecm.core.io.marshallers.json.document.DocumentPropertiesJsonReader; 044import org.nuxeo.ecm.core.io.registry.context.WrappedContext; 045import org.nuxeo.ecm.core.io.registry.reflect.Setup; 046import org.nuxeo.ecm.directory.BaseSession; 047import org.nuxeo.ecm.directory.Directory; 048import org.nuxeo.ecm.directory.DirectoryEntryResolver; 049import org.nuxeo.ecm.directory.Session; 050import org.nuxeo.ecm.directory.api.DirectoryEntry; 051import org.nuxeo.ecm.directory.api.DirectoryService; 052 053import com.fasterxml.jackson.databind.JsonNode; 054 055/** 056 * Convert Json as {@link DirectoryEntry}. 057 * <p> 058 * Format is (any additional json property is ignored): 059 * 060 * <pre> 061 * { 062 * "entity-type": "directoryEntry", 063 * "directoryName": "DIRECTORY_NAME", <- use it to update an existing document 064 * "properties": { 065 * <- entry properties depending on the directory schema (password fields are hidden) 066 * <- format is managed by {@link DocumentPropertiesJsonReader} 067 * } 068 * } 069 * </pre> 070 * 071 * @since 7.2 072 */ 073@Setup(mode = SINGLETON, priority = REFERENCE) 074public class DirectoryEntryJsonReader extends EntityJsonReader<DirectoryEntry> { 075 076 @Inject 077 private DirectoryService directoryService; 078 079 public DirectoryEntryJsonReader() { 080 super(ENTITY_TYPE); 081 } 082 083 @Override 084 protected DirectoryEntry readEntity(JsonNode jn) throws IOException { 085 String directoryName = getStringField(jn, "directoryName"); 086 Directory directory = directoryService.getDirectory(directoryName); 087 String schema = directory.getSchema(); 088 089 try (Session session = directory.getSession()) { 090 DocumentModel entry = null; 091 String id = getStringField(jn, "id"); 092 if (StringUtils.isNotBlank(id)) { 093 entry = session.getEntry(id); 094 } 095 096 JsonNode propsNode = jn.get("properties"); 097 if (propsNode != null && !propsNode.isNull() && propsNode.isObject()) { 098 if (entry == null) { 099 // backward compatibility; try to fetch the entry from the id inside the properties 100 id = getStringField(propsNode, directory.getIdField()); 101 entry = session.getEntry(id); 102 } 103 if (entry == null) { 104 entry = BaseSession.createEntryModel(schema, id, new HashMap<>()); 105 } 106 ParameterizedType genericType = TypeUtils.parameterize(List.class, Property.class); 107 try (Closeable resource = openWrappedContext(directory)) { 108 List<Property> properties = readEntity(List.class, genericType, propsNode); 109 for (Property property : properties) { 110 entry.setPropertyValue(property.getName(), property.getValue()); 111 } 112 } 113 return new DirectoryEntry(directory.getName(), entry); 114 } 115 } 116 return null; 117 } 118 119 /** 120 * Wraps and opens the current {@link org.nuxeo.ecm.core.io.registry.context.RenderingContext} with the directory 121 * schema and the parent resolver in case of hierarchical vocabulary. 122 */ 123 protected Closeable openWrappedContext(Directory directory) { 124 String directorySchema = directory.getSchema(); 125 126 WrappedContext specializedContext = ctx.wrap().with(DEFAULT_SCHEMA_NAME, directorySchema); 127 if (directorySchema.endsWith("xvocabulary")) { 128 // create dynamically a resolver for parent field because the schema can't hold the directory name 129 // for the resolver definition 130 DirectoryEntryResolver resolver = new DirectoryEntryResolver(); 131 resolver.configure(Map.of(PARAM_DIRECTORY, directory.getParentDirectory())); 132 specializedContext.with(FALLBACK_RESOLVER + "parent", resolver); 133 } 134 return specializedContext.open(); 135 } 136}