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.platform.usermanager.io;
021
022import static org.nuxeo.ecm.core.io.marshallers.json.document.DocumentPropertiesJsonReader.DEFAULT_SCHEMA_NAME;
023import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON;
024import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE;
025import static org.nuxeo.ecm.platform.usermanager.io.NuxeoGroupJsonWriter.ENTITY_TYPE;
026import static org.nuxeo.ecm.platform.usermanager.io.NuxeoGroupJsonWriter.GROUP_LABEL_COMPATIBILITY_FIELD;
027import static org.nuxeo.ecm.platform.usermanager.io.NuxeoGroupJsonWriter.GROUP_NAME_COMPATIBILITY_FIELD;
028import static org.nuxeo.ecm.platform.usermanager.io.NuxeoGroupJsonWriter.MEMBER_GROUPS_FETCH_PROPERTY;
029import static org.nuxeo.ecm.platform.usermanager.io.NuxeoGroupJsonWriter.MEMBER_USERS_FETCH_PROPERTY;
030import static org.nuxeo.ecm.platform.usermanager.io.NuxeoGroupJsonWriter.PARENT_GROUPS_FETCH_PROPERTY;
031
032import java.io.Closeable;
033import java.io.IOException;
034import java.lang.reflect.ParameterizedType;
035import java.util.ArrayList;
036import java.util.Arrays;
037import java.util.List;
038import java.util.Objects;
039
040import javax.inject.Inject;
041
042import org.apache.commons.lang3.StringUtils;
043import org.apache.commons.lang3.reflect.TypeUtils;
044import org.nuxeo.ecm.core.api.DocumentModel;
045import org.nuxeo.ecm.core.api.NuxeoGroup;
046import org.nuxeo.ecm.core.api.model.Property;
047import org.nuxeo.ecm.core.io.marshallers.json.EntityJsonReader;
048import org.nuxeo.ecm.core.io.registry.reflect.Setup;
049import org.nuxeo.ecm.platform.usermanager.GroupConfig;
050import org.nuxeo.ecm.platform.usermanager.NuxeoGroupImpl;
051import org.nuxeo.ecm.platform.usermanager.UserManager;
052
053import com.fasterxml.jackson.databind.JsonNode;
054
055/**
056 * Convert Json as {@link NuxeoGroup}.
057 * <p>
058 * Format is (any additional json property is ignored):
059 *
060 * <pre>
061 * {
062 *   "entity-type":"group",
063 *   "groupname": "GROUP_NAME", <- deprecated, for backward compatibility
064 *   "grouplabel": "GROUP_DISPLAY_NAME", <- deprecated, for backward compatibility
065 *   "id": "GROUP_NAME",
066 *   "memberUsers": [
067 *     "USERNAME1",
068 *     "USERNAME2",
069 *     ...
070 *   ],
071 *   "memberGroups": [
072 *     "GROUPNAME1",
073 *     "GROUPNAME2",
074 *     ...
075 *   ]
076 * }
077 * </pre>
078 * </p>
079 *
080 * @since 7.2
081 */
082@Setup(mode = SINGLETON, priority = REFERENCE)
083public class NuxeoGroupJsonReader extends EntityJsonReader<NuxeoGroup> {
084
085    @Inject
086    private UserManager userManager;
087
088    public NuxeoGroupJsonReader() {
089        super(ENTITY_TYPE);
090    }
091
092    @Override
093    protected NuxeoGroup readEntity(JsonNode jn) throws IOException {
094        GroupConfig groupConfig = userManager.getGroupConfig();
095        String id = getStringField(jn, "id");
096        String groupName = getStringField(jn, GROUP_NAME_COMPATIBILITY_FIELD);
097        if (StringUtils.isBlank(id) || (StringUtils.isNotBlank(groupName) && !id.equals(groupName))) {
098            // backward compatibility if `id` not found or if `groupname` is different
099            id = groupName;
100        }
101
102        DocumentModel groupModel = null;
103        if (StringUtils.isNotBlank(id)) {
104            groupModel = userManager.getGroupModel(id);
105        }
106        if (groupModel == null) {
107            groupModel = userManager.getBareGroupModel();
108            groupModel.setProperty(groupConfig.schemaName, groupConfig.idField, id);
109        }
110
111        String beforeReadLabel = (String) groupModel.getProperty(groupConfig.schemaName, groupConfig.labelField);
112
113        readProperties(groupModel, groupConfig, jn);
114        readMemberUsers(groupModel, groupConfig, jn);
115        readMemberGroups(groupModel, groupConfig, jn);
116        readParentGroups(groupModel, groupConfig, jn);
117
118        // override the `groupname` that may have been in the `properties` object
119        if (StringUtils.isNotBlank(id)) {
120            groupModel.setProperty(groupConfig.schemaName, groupConfig.idField, id);
121        }
122
123        // override with the compatibility `grouplabel` if needed
124        if (jn.has(GROUP_LABEL_COMPATIBILITY_FIELD)) {
125            String compatLabel = getStringField(jn, GROUP_LABEL_COMPATIBILITY_FIELD);
126            String label = (String) groupModel.getProperty(groupConfig.schemaName, groupConfig.labelField);
127            if (!Objects.equals(label, compatLabel)
128                    && (beforeReadLabel == null || Objects.equals(beforeReadLabel, label))) {
129                groupModel.setProperty(groupConfig.schemaName, groupConfig.labelField, compatLabel);
130            }
131        }
132
133        return new NuxeoGroupImpl(groupModel, groupConfig);
134    }
135
136    protected void readProperties(DocumentModel groupModel, GroupConfig groupConfig, JsonNode jn) throws IOException {
137        List<String> excludedProperties = Arrays.asList(groupConfig.membersField, groupConfig.subGroupsField,
138                groupConfig.parentGroupsField);
139        JsonNode propsNode = jn.get("properties");
140        if (propsNode != null && !propsNode.isNull() && propsNode.isObject()) {
141            ParameterizedType genericType = TypeUtils.parameterize(List.class, Property.class);
142            try (Closeable resource = ctx.wrap().with(DEFAULT_SCHEMA_NAME, groupConfig.schemaName).open()) {
143                List<Property> properties = readEntity(List.class, genericType, propsNode);
144                properties.stream().filter(p -> !excludedProperties.contains(p.getName())).forEach(
145                        p -> groupModel.setPropertyValue(p.getName(), p.getValue()));
146            }
147        }
148    }
149
150    protected void readMemberUsers(DocumentModel groupModel, GroupConfig groupConfig, JsonNode jn) {
151        if (jn.has(MEMBER_USERS_FETCH_PROPERTY)) {
152            List<String> users = getArrayStringValues(jn.get(MEMBER_USERS_FETCH_PROPERTY));
153            groupModel.setProperty(groupConfig.schemaName, groupConfig.membersField, users);
154        }
155    }
156
157    protected void readMemberGroups(DocumentModel groupModel, GroupConfig groupConfig, JsonNode jn) {
158        if (jn.has(MEMBER_GROUPS_FETCH_PROPERTY)) {
159            List<String> groups = getArrayStringValues(jn.get(MEMBER_GROUPS_FETCH_PROPERTY));
160            groupModel.setProperty(groupConfig.schemaName, groupConfig.subGroupsField, groups);
161        }
162    }
163
164    protected void readParentGroups(DocumentModel groupModel, GroupConfig groupConfig, JsonNode jn) {
165        if (jn.has(PARENT_GROUPS_FETCH_PROPERTY)) {
166            List<String> parents = getArrayStringValues(jn.get(PARENT_GROUPS_FETCH_PROPERTY));
167            groupModel.setProperty(groupConfig.schemaName, groupConfig.parentGroupsField, parents);
168        }
169    }
170
171    private List<String> getArrayStringValues(JsonNode node) {
172        List<String> values = new ArrayList<>();
173        if (node != null && !node.isNull() && node.isArray()) {
174            node.elements().forEachRemaining(n -> {
175                if (n != null && !n.isNull() && n.isTextual()) {
176                    values.add(n.textValue());
177                }
178            });
179        }
180        return values;
181    }
182
183}