com.bstek.dorado.data.entity.EntityEnhancer.java Source code

Java tutorial

Introduction

Here is the source code for com.bstek.dorado.data.entity.EntityEnhancer.java

Source

/*
 * This file is part of Dorado 7.x (http://dorado7.bsdn.org).
 * 
 * Copyright (c) 2002-2012 BSTEK Corp. All rights reserved.
 * 
 * This file is dual-licensed under the AGPLv3 (http://www.gnu.org/licenses/agpl-3.0.html) 
 * and BSDN commercial (http://www.bsdn.org/licenses) licenses.
 * 
 * If you are unsure which license is appropriate for your use, please contact the sales department
 * at http://www.bstek.com/contact.
 */

package com.bstek.dorado.data.entity;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import net.sf.cglib.beans.BeanMap;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.jexl2.JexlContext;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.bstek.dorado.core.Context;
import com.bstek.dorado.core.el.ExpressionHandler;
import com.bstek.dorado.core.resource.ResourceManager;
import com.bstek.dorado.core.resource.ResourceManagerUtils;
import com.bstek.dorado.data.provider.DataProvider;
import com.bstek.dorado.data.type.CustomEntityDataType;
import com.bstek.dorado.data.type.DataType;
import com.bstek.dorado.data.type.EntityDataType;
import com.bstek.dorado.data.type.property.BasePropertyDef;
import com.bstek.dorado.data.type.property.CacheMode;
import com.bstek.dorado.data.type.property.LazyPropertyDef;
import com.bstek.dorado.data.type.property.PropertyDef;
import com.bstek.dorado.data.type.property.Reference;
import com.bstek.dorado.data.type.validator.Validator;

/*
 * PropertyPath 1.
 * PropertyPath?BasePropertyDef?isKey=falseBasePropertyDef 2.
 * PropertyPath??DataTypePropertyDef 3.
 * PropertyPath?PropertyDef.name????????
 */

/**
 * @author Benny Bao (mailto:benny.bao@bstek.com)
 * @since 2010-12-19
 */
public abstract class EntityEnhancer {
    private static final ResourceManager resourceManager = ResourceManagerUtils.get(EntityEnhancer.class);

    private static final Log logger = LogFactory.getLog(EntityEnhancer.class);
    private static final String THIS = "this";

    protected static final Object UNDISPOSED_VALUE = new Object();

    private static ExpressionHandler expressionHandler;

    private static int maxEntityId = 0;
    private static long maxTimeStamp = 0;
    private static GetterInterceptionInjector injector = new GetterInterceptionInjector();

    private Set<String> propertiesHasRead;

    protected EntityDataType dataType;
    private int entityId;
    private long timeStamp;
    private EntityState state = EntityState.NONE;
    private boolean stateLocked = false;
    private Map<String, Object> exProperties;
    private Map<String, Object> oldValues;

    public EntityEnhancer(EntityDataType dataType) {
        setDataType(dataType);
    }

    public void setDataType(EntityDataType dataType) {
        this.dataType = dataType;
    }

    public EntityDataType getDataType() {
        return dataType;
    }

    private static ExpressionHandler getExpressionHandler() throws Exception {
        if (expressionHandler == null) {
            Context context = Context.getCurrent();
            expressionHandler = (ExpressionHandler) context.getServiceBean("expressionHandler");
        }
        return expressionHandler;
    }

    /**
     * ?dorado?read??<br>
     * ???EL???
     */
    public static void disableGetterInterception() {
        injector.disableGetterInterception();
    }

    /**
     * ?dorado?read??
     */
    public static void enableGetterInterception() {
        injector.resetHasPropertyResultSkiped();
        injector.enableGetterInterception();
    }

    /**
     * ???read??
     */
    public static boolean isGetterInterceptionDisabled() {
        return injector.isGetterInterceptionDisabled();
    }

    /**
     * ????
     */
    public static boolean hasGetterResultSkiped() {
        return injector.hasPropertyResultSkiped();
    }

    /**
     * ?
     */
    public static void setHasGetterResultSkiped() {
        injector.setHasPropertyResultSkiped();
    }

    /**
     * ???
     */
    public static void resetHasPropertyResultSkiped() {
        injector.resetHasPropertyResultSkiped();
    }

    public static synchronized int newEntityId() {
        if (maxEntityId >= (Integer.MAX_VALUE - 1)) {
            maxEntityId = 0;
        }
        return ++maxEntityId;
    }

    public static long getLastTimeStamp() {
        return maxTimeStamp;
    }

    public static synchronized long newTimeStamp() {
        if (maxTimeStamp >= (Long.MAX_VALUE - 1)) {
            maxTimeStamp = 0;
        }
        return ++maxTimeStamp;
    }

    public int getEntityId() {
        return entityId;
    }

    public void setEntityId(int entityId) {
        this.entityId = entityId;
    }

    public long getTimeStamp() {
        return timeStamp;
    }

    public void setTimeStamp(long timeStamp) {
        this.timeStamp = timeStamp;
    }

    public EntityState getState() {
        return state;
    }

    public void setState(EntityState state) {
        this.state = state;
    }

    public boolean isStateLocked() {
        return stateLocked;
    }

    public void setStateLocked(boolean stateLocked) {
        this.stateLocked = stateLocked;
    }

    protected abstract Set<String> doGetPropertySet(Object entity, boolean excludeExProperties);

    public Set<String> getPropertySet(Object entity, boolean excludeExProperties) {
        if (dataType == null || dataType.isAcceptUnknownProperty()) {
            return doGetPropertySet(entity, excludeExProperties);
        } else {
            return dataType.getPropertyDefs().keySet();
        }
    }

    protected Map<String, Object> getExProperties(boolean create) {
        if (exProperties == null && create) {
            exProperties = new HashMap<String, Object>();
        }
        return exProperties;
    }

    public Map<String, Object> getExProperties() {
        return exProperties;
    }

    /**
     * Map?Map??
     * 
     * @param create
     *            ???
     */
    public Map<String, Object> getOldValues(boolean create) {
        if (oldValues == null && create) {
            oldValues = new HashMap<String, Object>();
        }
        return oldValues;
    }

    /**
     * Map?Map??
     */
    public Map<String, Object> getOldValues() {
        return oldValues;
    }

    /**
     * ???
     */
    public void clearOldValues() {
        oldValues = null;
    }

    public boolean isLoaded(String property) {
        if (dataType != null) {
            PropertyDef propertyDef = dataType.getPropertyDef(property);
            if (propertyDef != null && propertyDef instanceof LazyPropertyDef) {
                return isPropertyHasRead(property);
            } else {
                throw new IllegalArgumentException(
                        "Property \"" + property + "\" does not exists or is not \"LazyPropertyDef\".");
            }
        } else {
            throw new IllegalArgumentException("DataType undefined.");
        }
    }

    public boolean loadIfNecessary(Object entity, String property) throws Throwable {
        if (dataType != null) {
            PropertyDef propertyDef = dataType.getPropertyDef(property);
            if (propertyDef != null && propertyDef instanceof LazyPropertyDef) {
                boolean hasRead = isPropertyHasRead(property);
                if (hasRead) {
                    EntityEnhancer.enableGetterInterception();
                    try {
                        readProperty(entity, property, false);
                    } finally {
                        EntityEnhancer.disableGetterInterception();
                    }
                }
                return !hasRead;
            } else {
                throw new IllegalArgumentException(
                        "Property \"" + property + "\" does not exists or is not \"LazyPropertyDef\".");
            }
        } else {
            throw new IllegalArgumentException("DataType undefined.");
        }
    }

    protected void markPropertyHasRead(String property) {
        if (propertiesHasRead == null) {
            propertiesHasRead = new HashSet<String>();
        }
        propertiesHasRead.add(property);
    }

    protected boolean isPropertyHasRead(String property) {
        return (propertiesHasRead == null) ? false : propertiesHasRead.contains(property);
    }

    protected abstract Object internalReadProperty(Object entity, String property) throws Exception;

    protected abstract void internalWriteProperty(Object entity, String property, Object value) throws Exception;

    protected final Object internalReadProperty(Object entity, String property, boolean isExProp) throws Exception {
        if (isExProp) {
            Map<String, Object> exProperties = getExProperties(false);
            return (exProperties != null) ? exProperties.get(property) : null;
        } else {
            return internalReadProperty(entity, property);
        }
    }

    protected final void internalWriteProperty(Object entity, String property, Object value, boolean isExProp)
            throws Exception {
        if (isExProp) {
            getExProperties(true).put(property, value);
        } else {
            internalWriteProperty(entity, property, value);
        }
    }

    protected Object interceptReadMethod(Object entity, String property, Object originResult, boolean isExProp)
            throws Throwable {
        Object result = originResult;
        if (dataType != null) {
            PropertyDef propertyDef = dataType.getPropertyDef(property);
            if (propertyDef != null) {
                DataType propertyDataType = propertyDef.getDataType();
                if (entity instanceof Map || propertyDataType == null
                        || !(propertyDataType instanceof CustomEntityDataType)) {
                    LazyPropertyDef lazyPropertyDef = null;
                    String propertyPath = null;
                    if (propertyDef instanceof LazyPropertyDef) {
                        lazyPropertyDef = (LazyPropertyDef) propertyDef;
                        if (result == null && !isPropertyHasRead(property) || lazyPropertyDef != null
                                && !CacheMode.isCacheableAtServerSide(lazyPropertyDef.getCacheMode())) {
                            result = readPropertyDef(entity, propertyDef, originResult);
                        }
                    } else {
                        BasePropertyDef basePropertyDef = (BasePropertyDef) propertyDef;
                        propertyPath = basePropertyDef.getPropertyPath();
                        if (StringUtils.isNotEmpty(propertyPath)) {
                            validatePropertyPath(basePropertyDef);
                            result = PropertyPathUtils.getValueByPath(dataType, entity, propertyPath);
                        }
                    }

                    if (!isGetterInterceptionDisabled()) {
                        if (result != null) {
                            Object proxy = EntityUtils.toEntity(result, propertyDataType);
                            if ((originResult == null || proxy != result) && StringUtils.isEmpty(propertyPath)) {
                                synchronized (this) {
                                    boolean originLocked = isStateLocked();
                                    setStateLocked(true);
                                    try {
                                        internalWriteProperty(entity, property, proxy, isExProp);
                                    } finally {
                                        setStateLocked(originLocked);
                                    }
                                }
                            }
                            result = proxy;
                        }

                        markPropertyHasRead(property);
                    }
                }
            }
        }
        return result;
    }

    private void validatePropertyPath(BasePropertyDef propertyDef) throws Exception {
        String propertyPath = propertyDef.getPropertyPath();
        String name = propertyDef.getName();
        if (propertyPath.equals(name) || propertyPath.startsWith(name + '.')) {
            throw new IllegalArgumentException(
                    resourceManager.getString("dorado.common/invalidatePropertyPath", name, propertyPath));
        }
    }

    protected boolean interceptWriteMethod(Object entity, String property, Object newValue, boolean isExProp)
            throws Exception {
        PropertyDef propertyDef = null;
        if (dataType != null) {
            propertyDef = dataType.getPropertyDef(property);
        }

        if (!isStateLocked()) {
            EntityState state = getState();
            if (propertyDef == null) {
                // TODO: ???????
                // if (!dataType.isAcceptUnknownProperty()) {
                // throw new IllegalArgumentException("Property [" +
                // property
                // + "] undefined in DataType [" + dataType.getName()
                // + "].");
                // }
            } else {
                if (propertyDef.getValidators() != null) {
                    for (Validator validator : propertyDef.getValidators()) {
                        validator.validate(newValue);
                    }
                }

                // TODO: ?ServerOldValue
                // if (state != EntityState.NEW
                // && propertyDef instanceof BasePropertyDef) {
                // Map<String, Object> oldValues = getOldValues(true);
                // if (!oldValues.containsKey(property)) {
                // Object originValue = internalReadProperty(entity,
                // property, isExProp);
                // if (originValue == null
                // || EntityUtils.isSimpleValue(originValue)) {
                // oldValues.put(property, originValue);
                // }
                // }
                // }
            }

            setTimeStamp(newTimeStamp());
            if (state == EntityState.NONE) {
                setState(EntityState.MODIFIED);
            }
        }

        if (propertyDef != null && propertyDef instanceof BasePropertyDef) {
            BasePropertyDef basePropertyDef = (BasePropertyDef) propertyDef;
            String propertyPath = basePropertyDef.getPropertyPath();
            if (StringUtils.isNotEmpty(propertyPath)) {
                validatePropertyPath(basePropertyDef);
                PropertyPathUtils.setValueByPath(dataType, entity, propertyPath, newValue);
                return false;
            }
        }
        return true;
    }

    private Object readPropertyDef(Object entity, PropertyDef propertyDef, Object originResult) throws Throwable {
        Object result;
        if (propertyDef instanceof Reference) {
            result = readReference(entity, (Reference) propertyDef, originResult);
        } else {
            throw new IllegalArgumentException("Unknown PropertyDef type [" + propertyDef + "].");
        }
        return result;
    }

    private Object readReference(Object entity, Reference referenceProperty, Object originResult) throws Throwable {
        if (!referenceProperty.shouldIntercept() || isGetterInterceptionDisabled()) {
            setHasGetterResultSkiped();
            return originResult;
        } else {
            DataProvider dataProvider = referenceProperty.getDataProvider();

            JexlContext jexlContext = getExpressionHandler().getJexlContext();
            Object originThis = jexlContext.get(THIS);
            jexlContext.set(THIS, entity);
            try {
                DataType dataType = referenceProperty.getDataType();
                return dataProvider.getResult(referenceProperty.getParameter(), dataType);
            } finally {
                jexlContext.set(THIS, originThis);
            }
        }
    }

    public Class<?> getPropertyType(Object entity, String property) {
        if (entity instanceof BeanMap) {
            return ((BeanMap) entity).getPropertyType(property);
        } else if (!(entity instanceof Map<?, ?>)) {
            try {
                return PropertyUtils.getPropertyType(entity, property);
            } catch (Exception e) {
                logger.warn(e, e);
            }
        }
        return null;
    }

    public abstract Object readProperty(Object entity, String property, boolean ignoreInterceptors)
            throws Throwable;

    public abstract void writeProperty(Object entity, String property, Object value) throws Throwable;

}

class GetterInterceptionInjectorCounter {
    boolean disabled;
    boolean hasPropertyResultSkiped;
}

class GetterInterceptionInjector extends ThreadLocal<GetterInterceptionInjectorCounter> {

    private GetterInterceptionInjectorCounter getCounter() {
        GetterInterceptionInjectorCounter counter = get();
        if (counter == null) {
            counter = new GetterInterceptionInjectorCounter();
            set(counter);
        }
        return counter;
    }

    private void throwInvalidState() {
        throw new IllegalStateException("Invalid entity interception state.");
    }

    public void disableGetterInterception() {
        GetterInterceptionInjectorCounter counter = getCounter();
        if (counter.disabled) {
            throwInvalidState();
        }
        counter.disabled = true;
    }

    public void enableGetterInterception() {
        GetterInterceptionInjectorCounter counter = getCounter();
        if (!counter.disabled) {
            throwInvalidState();
        }
        counter.disabled = false;
    }

    public boolean isGetterInterceptionDisabled() {
        GetterInterceptionInjectorCounter counter = get();
        return (counter == null) ? false : counter.disabled;
    }

    public boolean hasPropertyResultSkiped() {
        return getCounter().hasPropertyResultSkiped;
    }

    public void setHasPropertyResultSkiped() {
        getCounter().hasPropertyResultSkiped = true;
    }

    public void resetHasPropertyResultSkiped() {
        getCounter().hasPropertyResultSkiped = false;
    }
}