com.groupon.jenkins.mongo.JenkinsEmbeddedMapper.java Source code

Java tutorial

Introduction

Here is the source code for com.groupon.jenkins.mongo.JenkinsEmbeddedMapper.java

Source

/*
The MIT License (MIT)
    
Copyright (c) 2014, Groupon, Inc.
    
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
    
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
    
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
 */
package com.groupon.jenkins.mongo;

import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.thoughtworks.xstream.converters.reflection.SerializationMethodInvoker;
import hudson.security.Permission;
import hudson.util.CopyOnWriteList;
import org.bson.types.ObjectId;
import org.mongodb.morphia.Key;
import org.mongodb.morphia.annotations.Embedded;
import org.mongodb.morphia.mapping.CustomMapper;
import org.mongodb.morphia.mapping.MappedField;
import org.mongodb.morphia.mapping.Mapper;
import org.mongodb.morphia.mapping.MappingException;
import org.mongodb.morphia.mapping.cache.EntityCache;
import org.mongodb.morphia.utils.IterHelper;
import org.mongodb.morphia.utils.ReflectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

class JenkinsEmbeddedMapper implements CustomMapper {
    private final Map<Class, CustomMapper> customMappers;

    private final MapKeyValueMapper awkwardMapper;

    private final SerializationMethodInvoker serializationMethodInvoker;

    JenkinsEmbeddedMapper() {
        customMappers = new HashMap<Class, CustomMapper>();
        customMappers.put(CopyOnWriteList.class, new CopyOnWriteListMapper());

        serializationMethodInvoker = new SerializationMethodInvoker();

        awkwardMapper = new MapKeyValueMapper();
    }

    @Override
    public void toDBObject(Object entity, MappedField mf, DBObject dbObject, Map<Object, DBObject> involvedObjects,
            Mapper mapper) {
        Object fieldValue = mf.getFieldValue(entity);
        if (fieldValue == null)
            return; // nothing to do

        if (mapper.getConverters().hasSimpleValueConverter(mf)
                || mapper.getConverters().hasSimpleValueConverter(fieldValue)) {
            mapper.getOptions().getValueMapper().toDBObject(entity, mf, dbObject, involvedObjects, mapper);
        } else if (customMappers.containsKey(fieldValue.getClass())) {
            customMappers.get(fieldValue.getClass()).toDBObject(entity, mf, dbObject, involvedObjects, mapper);
        } else if (isAwkwardMap(mf)) {
            awkwardMapper.toDBObject(entity, mf, dbObject, involvedObjects, mapper);
        } else {
            // Genericly handle an object for serialization

            if (involvedObjects.containsKey(fieldValue)) { // already once serialized
                DBObject cachedStub = involvedObjects.get(fieldValue);

                if (cachedStub == null) { // visited, but not yet serialized, usually from mapper.toDBObject(...)
                    DBObject newStub = createStub(mapper, fieldValue, mapper.getId(fieldValue));
                    involvedObjects.put(fieldValue, newStub);
                    cachedStub = newStub;
                } else if (isFullySerializedObject(cachedStub, mapper)) {
                    // remove full object from the cache and replace it with an ObjectId reference
                    // so we're not storing redundant data
                    Object id = cachedStub.get(mapper.ID_KEY);
                    DBObject newStub = createStub(mapper, fieldValue, id);

                    involvedObjects.put(fieldValue, newStub);
                    cachedStub = newStub;
                }

                dbObject.put(mf.getNameToStore(), cachedStub); // serialize as the reference

            } else {
                mapper.getOptions().getEmbeddedMapper().toDBObject(entity, mf, dbObject, involvedObjects, mapper);
            }

        }
    }

    private boolean isFullySerializedObject(DBObject cachedStub, Mapper mapper) {
        return cachedStub.keySet().size() > 2 && cachedStub.containsField(mapper.ID_KEY)
                && cachedStub.containsField(mapper.CLASS_NAME_FIELDNAME);
    }

    private boolean isAwkwardMap(MappedField mf) {
        return mf.isMap() && Permission.class == mf.getMapKeyClass();
    }

    private DBObject createStub(Mapper mapper, Object fieldValue, Object id) {
        if (id == null) {
            id = new ObjectId();
        }

        DBObject stub = new BasicDBObject(mapper.CLASS_NAME_FIELDNAME, fieldValue.getClass().getName());
        stub.put(mapper.ID_KEY, id);

        return stub;
    }

    private Key extractKey(Mapper mapper, MappedField mf, DBObject dbObject) {
        if (mapper == null || mf == null || dbObject == null || (dbObject instanceof BasicDBList))
            return null;

        ObjectId objectId = (ObjectId) dbObject.get(mapper.ID_KEY);
        //HACKY GET RID OF SOON
        Object obj = mapper.getOptions().getObjectFactory().createInstance(mapper, mf, dbObject);

        if (objectId == null || obj == null)
            return null;

        return new Key(obj.getClass(), objectId);
    }

    @Override
    public void fromDBObject(DBObject dbObject, MappedField mf, Object entity, EntityCache cache, Mapper mapper) {

        Key key = extractKey(mapper, mf, (DBObject) dbObject.get(mf.getNameToStore()));
        if (key != null && cache.getEntity(key) != null) {
            Object object = cache.getEntity(key);
            mf.setFieldValue(entity, object);
        } else if (customMappers.containsKey(mf.getType())) {
            customMappers.get(mf.getType()).fromDBObject(dbObject, mf, entity, cache, mapper);
        } else if (isAwkwardMap(mf)) {
            awkwardMapper.fromDBObject(dbObject, mf, entity, cache, mapper);
        } else {
            // the first two conditions (isMap and isMultipleValues) are part of the hack to fix handling primitives
            if (mf.isMap()) {
                readMap(dbObject, mf, entity, cache, mapper);
            } else if (mf.isMultipleValues()) {
                readCollection(dbObject, mf, entity, cache, mapper);
            } else {
                mapper.getOptions().getEmbeddedMapper().fromDBObject(dbObject, mf, entity, cache, mapper);
            }

            serializationMethodInvoker.callReadResolve(mf.getFieldValue(entity));
        }

    }

    // Taken from Morphia's Embedded Mapper and is almost identical to that method
    @Deprecated
    private void readMap(final DBObject dbObject, final MappedField mf, final Object entity,
            final EntityCache cache, final Mapper mapper) {
        final Map map = mapper.getOptions().getObjectFactory().createMap(mf);

        final DBObject dbObj = (DBObject) mf.getDbObjectValue(dbObject);
        new IterHelper<Object, Object>().loopMap(dbObj, new IterHelper.MapIterCallback<Object, Object>() {
            @Override
            public void eval(final Object key, final Object val) {
                Object newEntity = null;

                //run converters
                if (val != null) {
                    if (mapper.getConverters().hasSimpleValueConverter(mf)
                            || mapper.getConverters().hasSimpleValueConverter(mf.getSubClass())) {
                        newEntity = mapper.getConverters().decode(mf.getSubClass(), val, mf);
                    } else if (mapper.getConverters().hasSimpleValueConverter(val.getClass())) { // added this condition to handle incorrectly mapped primitives.
                        newEntity = mapper.getConverters().decode(val.getClass(), val, mf);
                    } else {
                        if (val instanceof DBObject) {
                            newEntity = readMapOrCollectionOrEntity((DBObject) val, mf, cache, mapper);
                        } else {
                            throw new MappingException(
                                    "Embedded element isn't a DBObject! How can it be that is a " + val.getClass());
                        }

                    }
                }

                final Object objKey = mapper.getConverters().decode(mf.getMapKeyClass(), key);
                map.put(objKey, newEntity);
            }
        });

        if (!map.isEmpty()) {
            mf.setFieldValue(entity, map);
        }
    }

    // Taken from Morphia's Embedded Mapper
    @Deprecated
    private Object readMapOrCollectionOrEntity(DBObject dbObj, MappedField mf, EntityCache cache, Mapper mapper) {
        try {

            // to get around the protected access
            Method method = Mapper.class.getDeclaredMethod("fromDb", DBObject.class, Object.class,
                    EntityCache.class);
            method.setAccessible(true);
            if (Map.class.isAssignableFrom(mf.getSubClass()) || Iterable.class.isAssignableFrom(mf.getSubClass())) {
                final MapOrCollectionMF mocMF = new MapOrCollectionMF((ParameterizedType) mf.getSubType());

                method.invoke(mapper, dbObj, mocMF, cache);

                return mocMF.getValue();
            } else {
                final Object newEntity = mapper.getOptions().getObjectFactory().createInstance(mapper, mf, dbObj);
                return method.invoke(mapper, dbObj, newEntity, cache);
            }
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Mapper class does not have fromDb method", e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("Unable to call fromDb on Mapper with arguments", e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Security model prevents calling fromDb on Mapper", e);
        }
    }

    // Taken from Morphia's Embedded Mapper and is almost identical to that method
    @Deprecated
    private void readCollection(final DBObject dbObject, final MappedField mf, final Object entity,
            final EntityCache cache, final Mapper mapper) {
        // multiple documents in a List
        final Collection values = mf.isSet() ? mapper.getOptions().getObjectFactory().createSet(mf)
                : mapper.getOptions().getObjectFactory().createList(mf);

        final Object dbVal = mf.getDbObjectValue(dbObject);
        if (dbVal != null) {

            final List dbValues;
            if (dbVal instanceof List) {
                dbValues = (List) dbVal;
            } else {
                dbValues = new BasicDBList();
                dbValues.add(dbVal);
            }

            for (final Object o : dbValues) {

                Object newEntity = null;

                if (o != null) {
                    //run converters
                    if (mapper.getConverters().hasSimpleValueConverter(mf)
                            || mapper.getConverters().hasSimpleValueConverter(mf.getSubClass())) {
                        newEntity = mapper.getConverters().decode(mf.getSubClass(), o, mf);
                    } else if (mapper.getConverters().hasSimpleValueConverter(o.getClass())) {// added this condition to handle incorrectly mapped primitives.
                        newEntity = mapper.getConverters().decode(o.getClass(), o, mf);
                    } else {
                        if (o instanceof DBObject) {
                            newEntity = readMapOrCollectionOrEntity((DBObject) o, mf, cache, mapper);
                        } else {
                            throw new MappingException(
                                    "Embedded element isn't a DBObject! How can it be that is a " + o.getClass());
                        }
                    }
                }

                values.add(newEntity);
            }
        }
        if (!values.isEmpty() || mapper.getOptions().isStoreEmpties()) {
            if (mf.getType().isArray()) {
                mf.setFieldValue(entity,
                        ReflectionUtils.convertToArray(mf.getSubClass(), ReflectionUtils.iterToList(values)));
            } else {
                mf.setFieldValue(entity, values);
            }
        }
    }

}

// Taken from Morphia's Embedded Mapper
@Deprecated
class MapOrCollectionMF extends MappedField {
    private ParameterizedType pType;
    private Object value;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        final MapOrCollectionMF other = new MapOrCollectionMF();
        other.pType = pType;
        other.isSet = isSet;
        other.isMap = isMap;
        other.mapKeyType = mapKeyType;
        other.subType = subType;
        other.isMongoType = isMongoType;
        return other;
    }

    MapOrCollectionMF() {
        isSingleValue = false;
    }

    MapOrCollectionMF(final ParameterizedType t) {
        this();
        pType = t;
        final Class rawClass = (Class) t.getRawType();
        isSet = ReflectionUtils.implementsInterface(rawClass, Set.class);
        isMap = ReflectionUtils.implementsInterface(rawClass, Map.class);
        mapKeyType = getMapKeyClass();
        subType = getSubType();
        isMongoType = ReflectionUtils.isPropertyType(getSubClass());
    }

    public Object getValue() {
        return value;
    }

    @Override
    public String getNameToStore() {
        return "superFake";
    }

    @Override
    public Object getDbObjectValue(final DBObject dbObj) {
        return dbObj;
    }

    @Override
    public boolean hasAnnotation(final Class ann) {
        return Embedded.class.equals(ann);
    }

    @Override
    public String toString() {
        return "MapOrCollectionMF for " + super.toString();
    }

    @Override
    public Class getType() {
        return isMap ? Map.class : List.class;
    }

    @Override
    public Class getMapKeyClass() {
        return (Class) (isMap() ? pType.getActualTypeArguments()[0] : null);
    }

    @Override
    public Type getSubType() {
        return pType.getActualTypeArguments()[isMap() ? 1 : 0];
    }

    @Override
    public Class getSubClass() {
        return toClass(getSubType());
    }

    @Override
    public boolean isSingleValue() {
        return false;
    }

    @Override
    public Object getFieldValue(final Object classInst) {
        return value;
    }

    @Override
    public void setFieldValue(final Object classInst, final Object val) {
        value = val;
    }
}