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", &lt;- use it to update an existing document
064 *   "properties": {
065 *     &lt;- entry properties depending on the directory schema (password fields are hidden)
066 *     &lt;- 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}