org.mongojack.internal.util.SerializationUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.mongojack.internal.util.SerializationUtils.java

Source

/*
 * Copyright 2011 VZ Netzwerke Ltd
 * Copyright 2014 devbliss GmbH
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.mongojack.internal.util;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.bson.types.ObjectId;
import org.mongojack.DBQuery;
import org.mongojack.DBRef;
import org.mongojack.MongoJsonMappingException;
import org.mongojack.internal.ObjectIdSerializer;
import org.mongojack.internal.object.BsonObjectGenerator;
import org.mongojack.internal.query.CollectionQueryCondition;
import org.mongojack.internal.query.CompoundQueryCondition;
import org.mongojack.internal.query.QueryCondition;
import org.mongojack.internal.query.SimpleQueryCondition;
import org.mongojack.internal.update.MultiUpdateOperationValue;
import org.mongojack.internal.update.UpdateOperationValue;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.ContainerSerializer;
import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase;
import com.fasterxml.jackson.databind.ser.std.MapSerializer;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;

/**
 * Utilities for helping with serialisation
 */
public class SerializationUtils {

    private static final Set<Class<?>> BASIC_TYPES;

    static {
        Set<Class<?>> types = new HashSet<Class<?>>();
        types.add(String.class);
        types.add(Integer.class);
        types.add(Boolean.class);
        types.add(Short.class);
        types.add(Long.class);
        types.add(BigInteger.class);
        types.add(Float.class);
        types.add(Double.class);
        types.add(Byte.class);
        types.add(Character.class);
        types.add(BigDecimal.class);
        types.add(int[].class);
        types.add(boolean[].class);
        types.add(short[].class);
        types.add(long[].class);
        types.add(float[].class);
        types.add(double[].class);
        types.add(byte[].class);
        types.add(char[].class);
        types.add(Date.class);
        // Patterns are used by the regex method of the query builder
        types.add(Pattern.class);
        // Native types that we support
        types.add(ObjectId.class);
        types.add(DBRef.class);
        BASIC_TYPES = types;
    }

    /**
     * Serialize the fields of the given object using the given object mapper.
     * This will convert POJOs to DBObjects where necessary.
     * 
     * @param objectMapper
     *            The object mapper to use to do the serialization
     * @param object
     *            The object to serialize the fields of
     * @return The DBObject, safe for serialization to MongoDB
     */
    public static DBObject serializeFields(ObjectMapper objectMapper, DBObject object) {
        BasicDBObject serialised = null;
        for (String field : object.keySet()) {
            Object value = object.get(field);
            Object serialisedValue = serializeField(objectMapper, value);
            if (value != serialisedValue) {
                // It's changed
                if (serialised == null) {
                    // Make a shallow copy of the object
                    serialised = new BasicDBObject();
                    for (String f : object.keySet()) {
                        serialised.put(f, object.get(f));
                    }
                }
                serialised.put(field, serialisedValue);
            }
        }
        if (serialised != null) {
            return serialised;
        } else {
            return object;
        }
    }

    public static DBObject serializeQuery(ObjectMapper objectMapper, JavaType type, DBQuery.Query query) {
        SerializerProvider serializerProvider = JacksonAccessor.getSerializerProvider(objectMapper);
        JsonSerializer serializer = JacksonAccessor.findValueSerializer(serializerProvider, type);
        return serializeQuery(serializerProvider, serializer, query);
    }

    private static DBObject serializeQuery(SerializerProvider serializerProvider, JsonSerializer serializer,
            DBQuery.Query query) {
        DBObject serializedQuery = new BasicDBObject();
        for (Map.Entry<String, QueryCondition> field : query.conditions()) {
            String key = field.getKey();
            QueryCondition condition = field.getValue();
            serializedQuery.put(key, serializeQueryCondition(serializerProvider, serializer, key, condition));
        }
        return serializedQuery;
    }

    public static Object serializeQueryCondition(ObjectMapper objectMapper, JavaType type, String key,
            QueryCondition condition) {
        SerializerProvider serializerProvider = JacksonAccessor.getSerializerProvider(objectMapper);
        JsonSerializer serializer = JacksonAccessor.findValueSerializer(serializerProvider, type);
        return serializeQueryCondition(serializerProvider, serializer, key, condition);
    }

    private static Object serializeQueryCondition(SerializerProvider serializerProvider, JsonSerializer serializer,
            String key, QueryCondition condition) {
        if (condition instanceof SimpleQueryCondition) {
            SimpleQueryCondition simple = (SimpleQueryCondition) condition;
            if (!simple.requiresSerialization() || simple.getValue() == null) {
                return simple.getValue();
            } else {
                if (!key.startsWith("$")) {
                    serializer = findQuerySerializer(false, key, serializerProvider, serializer);
                }
                return serializeQueryField(simple.getValue(), serializer, serializerProvider, key);
            }
        } else if (condition instanceof CollectionQueryCondition) {
            CollectionQueryCondition coll = (CollectionQueryCondition) condition;
            if (!key.startsWith("$")) {
                serializer = findQuerySerializer(coll.targetIsCollection(), key, serializerProvider, serializer);
            }
            List<Object> serializedConditions = new ArrayList<Object>();
            for (QueryCondition item : coll.getValues()) {
                serializedConditions.add(serializeQueryCondition(serializerProvider, serializer, "$", item));
            }
            return serializedConditions;
        } else {
            CompoundQueryCondition compound = (CompoundQueryCondition) condition;
            if (!key.startsWith("$")) {
                serializer = findQuerySerializer(false, key, serializerProvider, serializer);
            }
            return serializeQuery(serializerProvider, serializer, compound.getQuery());
        }
    }

    private static Object serializeQueryField(Object value, JsonSerializer serializer,
            SerializerProvider serializerProvider, String op) {
        if (serializer == null) {
            if (value == null || BASIC_TYPES.contains(value.getClass())) {
                // Return as is
                return value;
            } else if (value instanceof Collection) {
                Collection<?> coll = (Collection<?>) value;
                List<Object> copy = null;
                int position = 0;
                for (Object item : coll) {
                    Object returned = serializeQueryField(item, null, serializerProvider, op);
                    if (returned != item) {
                        if (copy == null) {
                            copy = new ArrayList<Object>(coll);
                        }
                        copy.set(position, returned);
                    }
                    position++;
                }
                if (copy != null) {
                    return copy;
                } else {
                    return coll;
                }
            } else if (value.getClass().isArray()) {
                if (BASIC_TYPES.contains(value.getClass().getComponentType())) {
                    return value;
                }
                Object[] array = (Object[]) value;
                Object[] copy = null;
                for (int i = 0; i < array.length; i++) {
                    Object returned = serializeQueryField(array[i], null, serializerProvider, op);
                    if (returned != array[i]) {
                        if (copy == null) {
                            copy = new Object[array.length];
                            System.arraycopy(array, 0, copy, 0, array.length);
                        }
                        copy[i] = returned;
                    }
                }
                if (copy != null) {
                    return copy;
                } else {
                    return array;
                }
            } else {
                // We don't know what it is, just find a serializer for it
                serializer = JacksonAccessor.findValueSerializer(serializerProvider, value.getClass());
            }
        }
        BsonObjectGenerator objectGenerator = new BsonObjectGenerator();
        try {
            serializer.serialize(value, objectGenerator, serializerProvider);
        } catch (IOException e) {
            throw new MongoJsonMappingException("Error serializing value " + value + " in DBQuery operation " + op,
                    e);
        }
        return objectGenerator.getValue();
    }

    /**
     * Serialize the given field
     * 
     * @param objectMapper
     *            The object mapper to serialize it with
     * @param value
     *            The value to serialize
     * @return The serialized field. May return the same object if no
     *         serialization was necessary.
     */
    public static Object serializeField(ObjectMapper objectMapper, Object value) {
        if (value == null || BASIC_TYPES.contains(value.getClass())) {
            // Return as is
            return value;
        } else if (value instanceof DBObject) {
            return serializeFields(objectMapper, (DBObject) value);
        } else if (value instanceof Collection) {
            Collection<?> coll = (Collection<?>) value;
            List<Object> copy = null;
            int position = 0;
            for (Object item : coll) {
                Object returned = serializeField(objectMapper, item);
                if (returned != item) {
                    if (copy == null) {
                        copy = new ArrayList<Object>(coll);
                    }
                    copy.set(position, returned);
                }
                position++;
            }
            if (copy != null) {
                return copy;
            } else {
                return coll;
            }
        } else if (value.getClass().isArray()) {
            if (BASIC_TYPES.contains(value.getClass().getComponentType())) {
                return value;
            }
            Object[] array = (Object[]) value;
            Object[] copy = null;
            for (int i = 0; i < array.length; i++) {
                Object returned = serializeField(objectMapper, array[i]);
                if (returned != array[i]) {
                    if (copy == null) {
                        copy = new Object[array.length];
                        System.arraycopy(array, 0, copy, 0, array.length);
                    }
                    copy[i] = returned;
                }
            }
            if (copy != null) {
                return copy;
            } else {
                return array;
            }
        } else {
            // We don't know what it is, serialise it
            BsonObjectGenerator generator = new BsonObjectGenerator();
            try {
                objectMapper.writeValue(generator, value);
            } catch (JsonMappingException e) {
                throw new MongoJsonMappingException(e);
            } catch (IOException e) {
                throw new RuntimeException("Somehow got an IOException writing to memory", e);
            }
            return generator.getValue();
        }
    }

    public static DBObject serializeDBUpdate(Map<String, Map<String, UpdateOperationValue>> update,
            ObjectMapper objectMapper, JavaType javaType) {
        SerializerProvider serializerProvider = JacksonAccessor.getSerializerProvider(objectMapper);
        BasicDBObject dbObject = new BasicDBObject();

        JsonSerializer serializer = null;

        for (Map.Entry<String, Map<String, UpdateOperationValue>> op : update.entrySet()) {
            BasicDBObject opObject = new BasicDBObject();
            for (Map.Entry<String, UpdateOperationValue> field : op.getValue().entrySet()) {
                Object value;
                if (field.getValue().requiresSerialization()) {

                    if (serializer == null) {
                        serializer = JacksonAccessor.findValueSerializer(serializerProvider, javaType);
                    }

                    JsonSerializer fieldSerializer = findUpdateSerializer(field.getValue().isTargetCollection(),
                            field.getKey(), serializerProvider, serializer);
                    if (fieldSerializer != null) {
                        value = serializeUpdateField(field.getValue(), fieldSerializer, serializerProvider,
                                op.getKey(), field.getKey());
                    } else {
                        // Try default serializers
                        value = serializeField(objectMapper, field.getValue().getValue());
                    }
                } else {
                    value = field.getValue().getValue();
                }
                if (op.getKey().equals("$addToSet") && field.getValue() instanceof MultiUpdateOperationValue) {
                    // Add to set needs $each for multi values
                    opObject.put(field.getKey(), new BasicDBObject("$each", value));
                } else {
                    opObject.put(field.getKey(), value);
                }
            }
            dbObject.append(op.getKey(), opObject);
        }
        return dbObject;
    }

    private static Object serializeUpdateField(UpdateOperationValue value, JsonSerializer serializer,
            SerializerProvider serializerProvider, String op, String field) {
        if (value instanceof MultiUpdateOperationValue) {
            List<Object> results = new ArrayList<Object>();
            for (Object item : ((MultiUpdateOperationValue) value).getValues()) {
                results.add(serializeUpdateField(item, serializer, serializerProvider, op, field));
            }
            return results;
        } else {
            return serializeUpdateField(value.getValue(), serializer, serializerProvider, op, field);
        }
    }

    private static Object serializeUpdateField(Object value, JsonSerializer serializer,
            SerializerProvider serializerProvider, String op, String field) {
        BsonObjectGenerator objectGenerator = new BsonObjectGenerator();
        try {
            serializer.serialize(value, objectGenerator, serializerProvider);
        } catch (IOException e) {
            throw new MongoJsonMappingException(
                    "Error serializing value in DBUpdate operation " + op + " field " + field, e);
        }
        return objectGenerator.getValue();
    }

    private static JsonSerializer<?> findUpdateSerializer(boolean targetIsCollection, String fieldPath,
            SerializerProvider serializerProvider, JsonSerializer serializer) {
        if (serializer instanceof BeanSerializerBase) {
            JsonSerializer<?> fieldSerializer = serializer;
            // Iterate through the components of the field name
            String[] fields = fieldPath.split("\\.");
            for (String field : fields) {
                if (fieldSerializer == null) {
                    // We don't have a field serializer to look up the field on,
                    // so give up
                    return null;
                }
                if (field.equals("$") || field.matches("\\d+")) {
                    // The current serializer must be a collection
                    if (fieldSerializer instanceof ContainerSerializer) {
                        JsonSerializer contentSerializer = ((ContainerSerializer) fieldSerializer)
                                .getContentSerializer();
                        if (contentSerializer == null) {
                            // Work it out
                            JavaType contentType = ((ContainerSerializer) fieldSerializer).getContentType();
                            if (contentType != null) {
                                contentSerializer = JacksonAccessor.findValueSerializer(serializerProvider,
                                        contentType);
                            }
                        }
                        fieldSerializer = contentSerializer;
                    } else {
                        // Give up, don't attempt to serialise it
                        return null;
                    }
                } else if (fieldSerializer instanceof BeanSerializerBase) {
                    BeanPropertyWriter writer = JacksonAccessor.findPropertyWriter((BeanSerializerBase) serializer,
                            field);
                    if (writer != null) {
                        fieldSerializer = writer.getSerializer();
                        if (fieldSerializer == null) {
                            // Do a generic lookup
                            fieldSerializer = JacksonAccessor.findValueSerializer(serializerProvider,
                                    writer.getType());
                        }
                    } else {
                        // Give up
                        return null;
                    }
                } else if (fieldSerializer instanceof MapSerializer) {
                    fieldSerializer = ((MapSerializer) fieldSerializer).getContentSerializer();
                } else {
                    // Don't know how to find what the serialiser for this field
                    // is
                    return null;
                }
            }
            // Now we have a serializer for the field, see if we're supposed to
            // be serialising for a collection
            if (targetIsCollection) {
                if (fieldSerializer instanceof ContainerSerializer) {
                    fieldSerializer = ((ContainerSerializer) fieldSerializer).getContentSerializer();
                } else if (fieldSerializer instanceof ObjectIdSerializer) {
                    // Special case for ObjectIdSerializer, leave as is, the
                    // ObjectIdSerializer handles both single
                    // values as well as collections with no problems.
                } else {
                    // Give up
                    return null;
                }
            }
            return fieldSerializer;
        } else {
            return null;
        }
    }

    private static JsonSerializer<?> findQuerySerializer(boolean targetIsCollection, String fieldPath,
            SerializerProvider serializerProvider, JsonSerializer serializer) {
        if (serializer instanceof BeanSerializerBase || serializer instanceof MapSerializer) {
            JsonSerializer<?> fieldSerializer = serializer;
            // Iterate through the components of the field name
            String[] fields = fieldPath.split("\\.");
            for (String field : fields) {
                if (fieldSerializer == null) {
                    // We don't have a field serializer to look up the field on,
                    // so give up
                    return null;
                }

                boolean isIndex = field.matches("\\d+");

                // First step into the collection if there is one
                if (!isIndex) {
                    while (fieldSerializer instanceof ContainerSerializer) {
                        JsonSerializer contentSerializer = ((ContainerSerializer) fieldSerializer)
                                .getContentSerializer();
                        if (contentSerializer == null) {
                            // Work it out
                            JavaType contentType = ((ContainerSerializer) fieldSerializer).getContentType();
                            if (contentType != null) {
                                contentSerializer = JacksonAccessor.findValueSerializer(serializerProvider,
                                        contentType);
                            }
                        }
                        fieldSerializer = contentSerializer;
                    }
                }

                if (isIndex) {
                    if (fieldSerializer instanceof ContainerSerializer) {
                        JsonSerializer contentSerializer = ((ContainerSerializer) fieldSerializer)
                                .getContentSerializer();
                        if (contentSerializer == null) {
                            // Work it out
                            JavaType contentType = ((ContainerSerializer) fieldSerializer).getContentType();
                            if (contentType != null) {
                                contentSerializer = JacksonAccessor.findValueSerializer(serializerProvider,
                                        contentType);
                            }
                        }
                        fieldSerializer = contentSerializer;
                    } else {
                        // Give up, don't attempt to serialise it
                        return null;
                    }
                } else if (fieldSerializer instanceof BeanSerializerBase) {
                    BeanPropertyWriter writer = JacksonAccessor.findPropertyWriter((BeanSerializerBase) serializer,
                            field);
                    if (writer != null) {
                        fieldSerializer = writer.getSerializer();
                        if (fieldSerializer == null) {
                            // Do a generic lookup
                            fieldSerializer = JacksonAccessor.findValueSerializer(serializerProvider,
                                    writer.getType());
                        }
                    } else {
                        // Give up
                        return null;
                    }
                } else if (fieldSerializer instanceof MapSerializer) {
                    fieldSerializer = ((MapSerializer) fieldSerializer).getContentSerializer();
                } else {
                    // Don't know how to find what the serialiser for this field
                    // is
                    return null;
                }
            }
            // Now we have a serializer for the field, see if we're supposed to
            // be serialising for a collection
            if (targetIsCollection) {
                if (fieldSerializer instanceof ContainerSerializer) {
                    fieldSerializer = ((ContainerSerializer) fieldSerializer).getContentSerializer();
                } else if (fieldSerializer instanceof ObjectIdSerializer) {
                    // Special case for ObjectIdSerializer, leave as is, the
                    // ObjectIdSerializer handles both single
                    // values as well as collections with no problems.
                } else {
                    // Give up
                    return null;
                }
            }
            return fieldSerializer;
        } else {
            return null;
        }
    }
}