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