001/* 
002 * JKNIV, whinstone one contract to access your database.
003 * 
004 * Copyright (C) 2017, the original author or authors.
005 *
006 * This library is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 2.1 of the License.
010 * 
011 * This library is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 * 
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with this library; if not, write to the Free Software Foundation, Inc., 
018 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
019 */
020package net.sf.jkniv.whinstone.couchdb.commands;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032import com.fasterxml.jackson.core.JsonParser;
033import com.fasterxml.jackson.core.JsonProcessingException;
034import com.fasterxml.jackson.core.ObjectCodec;
035import com.fasterxml.jackson.databind.DeserializationContext;
036import com.fasterxml.jackson.databind.JsonDeserializer;
037import com.fasterxml.jackson.databind.JsonNode;
038
039import net.sf.jkniv.reflect.BasicType;
040import net.sf.jkniv.reflect.beans.ObjectProxy;
041import net.sf.jkniv.reflect.beans.ObjectProxyFactory;
042import net.sf.jkniv.reflect.beans.PropertyAccess;
043import net.sf.jkniv.whinstone.Queryable;
044import net.sf.jkniv.whinstone.couchdb.CouchDbResult;
045import net.sf.jkniv.whinstone.couchdb.ExecutionStats;
046
047@SuppressWarnings({"unchecked", "rawtypes" })
048public class CouchDbJsonDeserialization extends JsonDeserializer<CouchDbResultImpl>
049{
050    private final static Logger         LOG               = LoggerFactory.getLogger(CouchDbJsonDeserialization.class);
051    private final static PropertyAccess DEFAULT_ACCESS_ID = new PropertyAccess("id");
052    
053    @Override
054    public CouchDbResultImpl deserialize(JsonParser json, DeserializationContext ctxt)
055            throws IOException, JsonProcessingException
056    {
057        ObjectCodec codec = json.getCodec();
058        JsonNode field = codec.readTree(json);
059        Queryable queryable = JsonMapper.getCurrentQuery();
060        Class<?> returnType = queryable.getReturnType();
061        PropertyAccess accessId = DEFAULT_ACCESS_ID;
062        if (queryable.getDynamicSql() != null)
063            accessId = queryable.getDynamicSql().getSqlDialect().getAccessId();
064        
065        final Long totalRows = 0L;
066        final Long offset = 0L;
067        String bookmark = null;
068        String warning = null;
069        List rows = Collections.emptyList();
070        ExecutionStats stats = null;
071        if (field.hasNonNull("bookmark"))
072            bookmark = field.get("bookmark").asText();
073
074        if (field.hasNonNull("execution_stats"))
075            stats = JsonMapper.mapper(field.get("execution_stats").asText(), ExecutionStats.class);
076        
077        if (field.hasNonNull("warning"))
078        {
079            warning = field.get("warning").asText();
080            LOG.warn("Query [{}] warnning message: {}", queryable.getName(), warning);
081        }
082        
083        if (field.has("rows"))
084        {
085            final JsonNode nodeRows = field.get("rows");
086            if (CouchDbResult.class.isAssignableFrom(returnType))
087                rows = nodeToMap(nodeRows);
088            else
089                rows = parserViewResult(nodeRows, returnType, accessId);
090        }
091        else if (field.has("docs"))
092        {
093            final JsonNode nodeDocs = field.get("docs");
094            if (CouchDbResult.class.isAssignableFrom(returnType))
095                rows = nodeToMap(nodeDocs);
096            else
097                rows = parserFindResult(nodeDocs, returnType, accessId);
098        }
099        return CouchDbResultImpl.of(totalRows, offset, bookmark, warning, rows, stats);
100    }
101    
102    private List parserViewResult(final JsonNode nodeRows, Class<?> returnType, PropertyAccess accessId)
103    {
104        final List rows = new ArrayList();
105        if (nodeRows.isArray())
106        {
107            Iterator<JsonNode> it = nodeRows.elements();
108            while (it.hasNext())
109            {
110                JsonNode element = it.next();
111                JsonNode row = element.get("value");
112                if (element.has("doc"))
113                    row = element.get("doc");
114                
115                Object object = null;
116                if (BasicType.getInstance().isBasicType(returnType))
117                {
118                    if (!row.isNull())
119                    {
120                        if(row.isInt())
121                            object = row.asInt();
122                        else if(row.isLong())
123                            object = row.asLong();
124                        else if(row.isDouble())
125                            object = row.asDouble();
126                        else if (row.isBoolean())
127                            object = row.asBoolean();
128                        else if (row.isTextual())
129                            object = row.asText();
130                    }
131                }
132                else if (row.isObject())
133                    object = parserRow(returnType, accessId, element, row);
134                else
135                    object = JsonMapper.mapper(element.toString(), returnType);
136                rows.add(object);
137            }
138        }
139        return rows;
140    }
141    
142    private List parserFindResult(final JsonNode nodeRows, Class<?> returnType, PropertyAccess accessId)
143    {
144        final List rows = new ArrayList();
145        if (nodeRows.isArray())
146        {
147            Iterator<JsonNode> it = nodeRows.elements();
148            while (it.hasNext())
149            {
150                JsonNode row = it.next();
151                Object object = parserRow(returnType, accessId, null, row);
152                rows.add(object);
153            }
154        }
155        return rows;
156    }
157    
158    private Object parserRow(Class<?> returnType, PropertyAccess accessId, JsonNode element, JsonNode row)
159    {
160        Object object = null;
161        if (row.isObject())
162        {
163            object = JsonMapper.mapper(row.toString(), returnType);
164            if (element != null)
165                setIdentity(element, object, accessId);
166        }
167        else
168        {
169            LOG.error("the value node from json result isn't an object. Cannot parse it to returnType {}", returnType);
170        }
171        return object;
172    }
173    
174    private void setIdentity(JsonNode element, Object row, PropertyAccess accessId)
175    {
176        String id = null;
177        String key = null;
178        if (element.hasNonNull("id"))
179            id = element.get("id").asText();
180        if (element.hasNonNull("key"))
181            key = element.get("key").asText();
182        
183        if (row instanceof Map)
184        {
185            ((Map) row).put("id", id);
186            ((Map) row).put("key", key);
187        }
188        else
189        {
190            ObjectProxy<?> proxy = ObjectProxyFactory.of(row);
191            if (proxy.hasMethod(accessId.getWriterMethodName()))
192                proxy.invoke(accessId.getWriterMethodName(), id);
193            if (proxy.hasMethod("setKey"))
194                proxy.invoke("setKey", key);
195        }
196        
197    }
198
199    private List nodeToMap(final JsonNode nodeRows)
200    {
201        final List rows = new ArrayList();
202        Iterator<JsonNode> it = nodeRows.elements();
203        while (it.hasNext())
204        {
205            JsonNode row = it.next();
206            Map map = JsonMapper.mapper(row.toString(), Map.class);
207            rows.add(map);
208        }
209        return rows;
210    }
211}