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