001/*
002 * (C) Copyright 2017 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 *     Funsho David
018 *
019 */
020
021package org.nuxeo.mongodb.core;
022
023import java.io.Serializable;
024import java.lang.reflect.Array;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Calendar;
028import java.util.Collections;
029import java.util.Date;
030import java.util.HashMap;
031import java.util.List;
032import java.util.Map;
033
034import org.bson.Document;
035
036/**
037 * Helper for serialization/deserialization of BSON objects
038 *
039 * @since 9.1
040 */
041public class MongoDBSerializationHelper {
042
043    public static final String MONGODB_ID = "_id";
044
045    public static final String MONGODB_SET = "$set";
046
047    public static final String MONGODB_INC = "$inc";
048
049    public static final String MONGODB_SEQ = "seq";
050
051    private MongoDBSerializationHelper() {
052        // empty
053    }
054
055    /**
056     * Create a BSON object with a single field from a pair key/value
057     *
058     * @param key the key which corresponds to the field id in the object
059     * @param value the value which corresponds to the field value in the object
060     * @return the new BSON object
061     */
062    public static Document fieldMapToBson(String key, Object value) {
063        return fieldMapToBson(Collections.singletonMap(key, value));
064    }
065
066    /**
067     * Create a BSON object from a map
068     *
069     * @param fieldMap a map of keys/values
070     * @return the new BSON object
071     */
072    public static Document fieldMapToBson(Map<String, Object> fieldMap) {
073        Document doc = new Document();
074        for (Map.Entry<String, Object> entry : fieldMap.entrySet()) {
075            Object val = valueToBson(entry.getValue());
076            if (val != null) {
077                doc.put(entry.getKey(), val);
078            }
079        }
080        return doc;
081    }
082
083    /**
084     * Cast an object according to its instance
085     *
086     * @param value the object to transform
087     * @return the BSON object
088     */
089    public static Object valueToBson(Object value) {
090        if (value instanceof Map) {
091            @SuppressWarnings("unchecked")
092            Map<String, Object> map = (Map<String, Object>) value;
093            return fieldMapToBson(map);
094        } else if (value instanceof List) {
095            @SuppressWarnings("unchecked")
096            List<Object> values = (List<Object>) value;
097            return listToBson(values);
098        } else if (value instanceof Object[]) {
099            return listToBson(Arrays.asList((Object[]) value));
100        } else {
101            return serializableToBson(value);
102        }
103    }
104
105    protected static List<Object> listToBson(List<Object> values) {
106        List<Object> objects = new ArrayList<>(values.size());
107        for (Object value : values) {
108            objects.add(valueToBson(value));
109        }
110        return objects;
111    }
112
113    protected static Object serializableToBson(Object value) {
114        if (value instanceof Calendar) {
115            return ((Calendar) value).getTime();
116        }
117        return value;
118    }
119
120    /**
121     * Create a map from a BSON object
122     *
123     * @param doc the BSON object to parse
124     * @return the new map
125     */
126    public static Map<String, Object> bsonToFieldMap(Document doc) {
127        Map<String, Object> fieldMap = new HashMap<>();
128        for (String key : doc.keySet()) {
129            if (MONGODB_ID.equals(key)) {
130                // skip native id
131                continue;
132            }
133            fieldMap.put(key, bsonToValue(doc.get(key)));
134        }
135        return fieldMap;
136    }
137
138    protected static Serializable bsonToValue(Object value) {
139        if (value instanceof List) {
140            @SuppressWarnings("unchecked")
141            List<Object> list = (List<Object>) value;
142            if (list.isEmpty()) {
143                return null;
144            } else {
145                Class<?> klass = Object.class;
146                for (Object o : list) {
147                    if (o != null) {
148                        klass = scalarToSerializableClass(o.getClass());
149                        break;
150                    }
151                }
152                if (Document.class.isAssignableFrom(klass)) {
153                    List<Serializable> l = new ArrayList<>(list.size());
154                    for (Object o : list) {
155                        l.add((Serializable) bsonToFieldMap((Document) o));
156                    }
157                    return (Serializable) l;
158                } else {
159                    // turn the list into a properly-typed array
160                    Object[] array = (Object[]) Array.newInstance(klass, list.size());
161                    int i = 0;
162                    for (Object o : list) {
163                        array[i++] = scalarToSerializable(o);
164                    }
165                    return array;
166                }
167            }
168        } else if (value instanceof Document) {
169            return (Serializable) bsonToFieldMap((Document) value);
170        } else {
171            return scalarToSerializable(value);
172        }
173    }
174
175    protected static Class<?> scalarToSerializableClass(Class<?> klass) {
176        if (Date.class.isAssignableFrom(klass)) {
177            return Calendar.class;
178        }
179        return klass;
180    }
181
182    protected static Serializable scalarToSerializable(Object value) {
183        if (value instanceof Date) {
184            Calendar cal = Calendar.getInstance();
185            cal.setTime((Date) value);
186            return cal;
187        }
188        return (Serializable) value;
189    }
190
191}