act.storage.db.impl.morphia.MorphiaDbHooker.java Source code

Java tutorial

Introduction

Here is the source code for act.storage.db.impl.morphia.MorphiaDbHooker.java

Source

package act.storage.db.impl.morphia;

/*-
 * #%L
 * ACT Storage
 * %%
 * Copyright (C) 2015 - 2017 ActFramework
 * %%
 * 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.
 * #L%
 */

import act.Act;
import act.app.App;
import act.app.DbServiceManager;
import act.db.DeleteEvent;
import act.db.morphia.MorphiaService;
import act.event.ActEventListenerBase;
import act.storage.StorageServiceManager;
import act.storage.UpdatePolicy;
import act.storage.db.DbHooker;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import org.mongodb.morphia.AbstractEntityInterceptor;
import org.mongodb.morphia.EntityInterceptor;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Transient;
import org.mongodb.morphia.mapping.Mapper;
import org.osgl.$;
import org.osgl.cache.CacheService;
import org.osgl.logging.LogManager;
import org.osgl.logging.Logger;
import org.osgl.storage.ISObject;
import org.osgl.storage.IStorageService;
import org.osgl.util.C;
import org.osgl.util.S;

import java.lang.reflect.Field;
import java.util.*;

/**
 * hook to {@link act.db.morphia.MorphiaPlugin morphia db layer}
 */
public class MorphiaDbHooker implements DbHooker {

    private static Logger logger = LogManager.get(MorphiaDbHooker.class);

    private StorageServiceManager ssm;

    public MorphiaDbHooker() {
    }

    @Override
    public Class entityAnnotation() {
        return (Entity.class);
    }

    @Override
    public Class transientAnnotationType() {
        return (Transient.class);
    }

    @Override
    public void hookLifecycleInterceptors() {
        DbServiceManager dbServiceManager = Act.app().dbServiceManager();
        List<MorphiaService> morphiaServices = dbServiceManager.dbServicesByClass(MorphiaService.class);
        StorageFieldConverter storageFieldConverter = new StorageFieldConverter(ssm());
        for (MorphiaService morphiaService : morphiaServices) {
            morphiaService.mapper().addInterceptor(storageFieldConverter);
        }
    }

    @Override
    public int hashCode() {
        return getClass().getName().hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return this == obj || null != obj && MorphiaDbHooker.class.getName().equals(obj.getClass().getName());
    }

    private synchronized StorageServiceManager ssm() {
        if (null == ssm) {
            ssm = App.instance().singleton(StorageServiceManager.class);
        }
        return ssm;
    }
}

class StorageFieldConverter extends AbstractEntityInterceptor implements EntityInterceptor {

    private static Logger logger = LogManager.get(MorphiaDbHooker.class);

    private StorageServiceManager ssm;

    private CacheService cacheService;

    private Map<$.T2<Class, String>, Class> fieldCache = new HashMap<>();

    StorageFieldConverter(StorageServiceManager ssm) {
        this.ssm = $.requireNotNull(ssm);
        this.cacheService = Act.app().cache("storage-morphia");
        Act.app().eventBus().bindAsync(DeleteEvent.class, new ActEventListenerBase<DeleteEvent>() {
            @Override
            public void on(DeleteEvent eventObject) throws Exception {
                onDelete(eventObject.getSource());
            }
        });
    }

    private void onDelete(Object entity) {
        Class c = entity.getClass();
        String cn = c.getName();
        List<String> storageFields = ssm.managedFields(c);
        for (String fieldName : storageFields) {
            boolean isCollection = ssm.isCollection(cn, fieldName);
            String keyCacheField = StorageServiceManager.keyCacheField(fieldName);
            if (!isCollection) {
                String key = $.getProperty(cacheService, entity, keyCacheField);
                if (S.blank(key)) {
                    continue;
                }
                IStorageService ss = ssm.storageService(c, fieldName);
                try {
                    ss.remove(key);
                } catch (Exception e) {
                    logger.warn(e, "Error deleting sobject by key: %s", key);
                }
            } else {
                Collection<String> keys = $.getProperty(cacheService, entity, keyCacheField);
                if (null != keys) {
                    for (String key : keys) {
                        if (S.blank(key)) {
                            continue;
                        }
                        IStorageService ss = ssm.storageService(c, fieldName);
                        try {
                            ss.remove(key);
                        } catch (Exception e) {
                            logger.warn(e, "Error deleting sobject by key: %s", key);
                        }
                    }
                }
            }
        }
    }

    @Override
    public void postLoad(Object ent, DBObject dbObj, Mapper mapper) {
        Class c = ent.getClass();
        String cn = c.getName();
        List<String> storageFields = ssm.managedFields(c);
        for (String fieldName : storageFields) {
            boolean isCollection = ssm.isCollection(cn, fieldName);
            if (!isCollection) {
                String key = ((BasicDBObject) dbObj).getString(fieldName);
                if (S.blank(key)) {
                    continue;
                }
                IStorageService ss = ssm.storageService(c, fieldName);
                try {
                    ISObject sobj = ss.get(key);
                    $.setProperty(ent, sobj, fieldName);
                    $.setProperty(ent, key, StorageServiceManager.keyCacheField(fieldName));
                } catch (Exception e) {
                    logger.warn(e, "Error loading sobject by key: %s", key);
                }
            } else {
                IStorageService ss = ssm.storageService(c, fieldName);
                Class fieldType = fieldType(c, fieldName);
                Collection<ISObject> sobjs = $.cast(Act.app().getInstance(fieldType));
                Collection<String> keys = (Collection<String>) ((BasicDBObject) dbObj).get(fieldName);
                if (null == keys) {
                    keys = C.list();
                }
                for (String key : keys) {
                    try {
                        ISObject sobj = ss.get(key);
                        sobjs.add(sobj);
                    } catch (Exception e) {
                        logger.warn(e, "Error loading sobject by key: %s", key);
                    }
                }
                $.setProperty(ent, sobjs, fieldName);
                $.setProperty(ent, keys, StorageServiceManager.keyCacheField(fieldName));
            }
        }
    }

    @Override
    public void prePersist(Object ent, DBObject dbObj, Mapper mapper) {
        Class c = ent.getClass();
        String cn = c.getName();
        List<String> storageFields = ssm.managedFields(c);
        for (String fieldName : storageFields) {
            UpdatePolicy updatePolicy = ssm.updatePolicy(c, fieldName);
            IStorageService ss = ssm.storageService(c, fieldName);
            String keyCacheField = StorageServiceManager.keyCacheField(fieldName);
            boolean isCollection = ssm.isCollection(cn, fieldName);
            if (!isCollection) {
                ISObject sobj = $.getProperty(cacheService, ent, fieldName);
                String newKey = null == sobj ? null : sobj.getKey();
                String prevKey = $.getProperty(cacheService, ent, keyCacheField);
                updatePolicy.handleUpdate(prevKey, newKey, ss);
                if (null != sobj) {
                    if (S.blank(newKey) || !ss.isManaged(sobj)) {
                        newKey = ss.getKey();
                    }
                    if (S.neq(newKey, prevKey)) {
                        try {
                            sobj = ss.put(newKey, sobj);
                            $.setProperty(cacheService, ent, newKey, keyCacheField);
                            $.setProperty(cacheService, ent, sobj, fieldName);
                        } catch (Exception e) {
                            logger.warn(e, "Error persist sobject by key: %s", newKey);
                        }
                    }
                    dbObj.put(fieldName, sobj.getKey());
                }
            } else {
                // The field is a collection of ISObject.
                // 1. handle obsolete sobject items
                Collection<ISObject> col = $.getProperty(cacheService, ent, fieldName);
                if (null == col) {
                    col = C.newList();
                }
                Set<String> newKeys = C.newSet();
                for (ISObject sobj : col) {
                    if (null == sobj) {
                        continue;
                    }
                    newKeys.add(sobj.getKey());
                }
                Set<String> oldKeys = $.getProperty(cacheService, ent, keyCacheField);
                if (null == oldKeys) {
                    oldKeys = C.newSet();
                }
                Set<String> oldKeysCopy = C.newSet(oldKeys);
                oldKeysCopy.removeAll(newKeys);
                for (String toBeRemoved : oldKeysCopy) {
                    if (S.isBlank(toBeRemoved)) {
                        continue;
                    }
                    updatePolicy.handleUpdate(toBeRemoved, null, ss);
                }
                // 2. persist all new sobject items
                Class fieldType = fieldType(c, fieldName);
                Collection<ISObject> updatedCol = $.cast(Act.app().getInstance(fieldType));
                newKeys.clear();
                for (ISObject sobj : col) {
                    if (null == sobj) {
                        continue;
                    }
                    String newKey = sobj.getKey();
                    if (S.blank(newKey) || !ss.isManaged(sobj)) {
                        newKey = ss.getKey();
                    }
                    if (!oldKeys.contains(newKey)) {
                        try {
                            sobj = ss.put(newKey, sobj);
                            updatedCol.add(sobj);
                            newKeys.add(sobj.getKey());
                        } catch (Exception e) {
                            logger.warn(e, "Error persist sobject by key: %s", newKey);
                        }
                    }
                }
                $.setProperty(cacheService, ent, newKeys, keyCacheField);
                $.setProperty(cacheService, ent, updatedCol, fieldName);
                dbObj.put(fieldName, newKeys);
            }
        }
    }

    private Class<?> fieldType(Class hostClass, String fieldName) {
        $.T2<Class, String> fieldCacheKey = $.T2(hostClass, fieldName);
        Class fieldType = fieldCache.get(fieldCacheKey);
        if (null == fieldType) {
            Field field = $.fieldOf(hostClass, fieldName, false);
            fieldType = field.getType();
            fieldCache.put(fieldCacheKey, fieldType);
        }
        return fieldType;
    }
}