001/*
002 * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     dmetzler
016 */
017package org.nuxeo.ecm.restapi.jaxrs.io.usermanager;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.io.Serializable;
022import java.lang.annotation.Annotation;
023import java.lang.reflect.Type;
024import java.util.ArrayList;
025import java.util.List;
026
027import javax.ws.rs.Consumes;
028import javax.ws.rs.WebApplicationException;
029import javax.ws.rs.core.Context;
030import javax.ws.rs.core.MediaType;
031import javax.ws.rs.core.MultivaluedMap;
032import javax.ws.rs.core.Response;
033import javax.ws.rs.ext.MessageBodyReader;
034import javax.ws.rs.ext.Provider;
035
036import org.apache.commons.io.IOUtils;
037import org.apache.commons.logging.Log;
038import org.apache.commons.logging.LogFactory;
039import org.codehaus.jackson.JsonFactory;
040import org.codehaus.jackson.JsonParseException;
041import org.codehaus.jackson.JsonParser;
042import org.codehaus.jackson.JsonToken;
043import org.nuxeo.ecm.core.api.DocumentModel;
044import org.nuxeo.ecm.core.api.NuxeoException;
045import org.nuxeo.ecm.core.api.NuxeoPrincipal;
046import org.nuxeo.ecm.core.api.PropertyException;
047import org.nuxeo.ecm.platform.usermanager.NuxeoPrincipalImpl;
048import org.nuxeo.ecm.platform.usermanager.UserManager;
049import org.nuxeo.ecm.webengine.WebException;
050import org.nuxeo.runtime.api.Framework;
051
052/**
053 * Class that knows how to read NuxeoPrincipal from JSON
054 *
055 * @since 5.7.3
056 */
057@Provider
058@Consumes({ "application/json+nxentity", "application/json" })
059public class NuxeoPrincipalReader implements MessageBodyReader<NuxeoPrincipal> {
060
061    protected static final Log log = LogFactory.getLog(NuxeoPrincipalReader.class);
062
063    @Context
064    JsonFactory factory;
065
066    @Override
067    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
068        return NuxeoPrincipal.class.isAssignableFrom(type);
069    }
070
071    @Override
072    public NuxeoPrincipal readFrom(Class<NuxeoPrincipal> type, Type genericType, Annotation[] annotations,
073            MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
074            throws IOException, WebApplicationException {
075
076        String content = IOUtils.toString(entityStream);
077        if (content.isEmpty()) {
078            throw new WebException("No content in request body", Response.Status.BAD_REQUEST.getStatusCode());
079        }
080
081        return readRequest(content, httpHeaders);
082
083    }
084
085    /**
086     * @param content
087     * @param httpHeaders
088     * @return
089     * @since 5.7.3
090     */
091    private NuxeoPrincipal readRequest(String content, MultivaluedMap<String, String> httpHeaders) {
092        try {
093            JsonParser jp = factory.createJsonParser(content);
094            return readJson(jp, httpHeaders);
095        } catch (NuxeoException | IOException e) {
096            throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR);
097        }
098    }
099
100    /**
101     * @param jp
102     * @param httpHeaders
103     * @param request2
104     * @return
105     * @throws IOException
106     * @throws JsonParseException
107     * @since 5.7.3
108     */
109    static NuxeoPrincipal readJson(JsonParser jp, MultivaluedMap<String, String> httpHeaders)
110            throws JsonParseException, IOException {
111        JsonToken tok = jp.nextToken();
112
113        // skip {
114        if (jp.getCurrentToken() == JsonToken.START_OBJECT) {
115            tok = jp.nextToken();
116        }
117        String id = null;
118
119        UserManager um = Framework.getLocalService(UserManager.class);
120        DocumentModel userDoc = null;
121        String schema = um.getUserSchemaName();
122
123        while (tok != JsonToken.END_OBJECT) {
124            String key = jp.getCurrentName();
125            jp.nextToken();
126            if ("id".equals(key)) {
127                id = jp.readValueAs(String.class);
128
129                NuxeoPrincipal principal = um.getPrincipal(id);
130                if (principal == null) {
131                    userDoc = um.getBareUserModel();
132                } else {
133                    userDoc = principal.getModel();
134                }
135            } else if ("extendedGroups".equals(key)) {
136                jp.readValueAsTree();
137            } else if ("properties".equals(key)) {
138                readProperties(jp, userDoc, schema);
139            } else if ("entity-type".equals(key)) {
140                String entityType = jp.readValueAs(String.class);
141                if (!"user".equals(entityType)) {
142                    throw new WebApplicationException(Response.Status.BAD_REQUEST);
143                }
144            }
145            tok = jp.nextToken();
146        }
147
148        NuxeoPrincipal principal = new NuxeoPrincipalImpl(id);
149        principal.setModel(userDoc);
150
151        return principal;
152
153    }
154
155    protected static void readProperties(JsonParser jp, DocumentModel doc, String schemaName) throws PropertyException,
156            JsonParseException, IOException {
157        JsonToken tok = jp.nextToken();
158        while (tok != JsonToken.END_OBJECT) {
159
160            String key = schemaName + ":" + jp.getCurrentName();
161            tok = jp.nextToken();
162            if (tok == JsonToken.START_ARRAY) {
163                doc.setPropertyValue(key, (Serializable) readArrayProperty(jp));
164            } else if (tok == JsonToken.VALUE_NULL) {
165                doc.setPropertyValue(key, (String) null);
166            } else {
167                doc.setPropertyValue(key, jp.getText());
168            }
169            tok = jp.nextToken();
170        }
171    }
172
173    protected static List<Serializable> readArrayProperty(JsonParser jp) throws JsonParseException, IOException {
174        List<Serializable> list = new ArrayList<>();
175        JsonToken tok = jp.nextToken();
176        while (tok != JsonToken.END_ARRAY) {
177            if (tok == JsonToken.START_ARRAY) {
178                list.add((Serializable) readArrayProperty(jp));
179            } else {
180                list.add(jp.getText());
181            }
182            tok = jp.nextToken();
183        }
184        return list;
185    }
186
187}