com.mmnaseri.dragonfly.entity.impl.DefaultEntityContext.java Source code

Java tutorial

Introduction

Here is the source code for com.mmnaseri.dragonfly.entity.impl.DefaultEntityContext.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2013 Milad Naseri.
 *
 * 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.mmnaseri.dragonfly.entity.impl;

import com.mmnaseri.couteau.basics.api.Cache;
import com.mmnaseri.couteau.basics.api.Transformer;
import com.mmnaseri.couteau.basics.api.impl.ConcurrentCache;
import com.mmnaseri.couteau.enhancer.api.ClassEnhancer;
import com.mmnaseri.couteau.enhancer.impl.GeneratingClassEnhancer;
import com.mmnaseri.couteau.lang.compiler.impl.DefaultDynamicClassCompiler;
import com.mmnaseri.couteau.lang.compiler.impl.SimpleJavaSourceCompiler;
import com.mmnaseri.couteau.reflection.cp.ConfigurableClassLoader;
import com.mmnaseri.couteau.reflection.util.ClassUtils;
import com.mmnaseri.dragonfly.cg.StaticNamingPolicy;
import com.mmnaseri.dragonfly.data.DataAccess;
import com.mmnaseri.dragonfly.data.DataAccessSession;
import com.mmnaseri.dragonfly.entity.*;
import com.mmnaseri.dragonfly.error.EntityContextReInitializationError;
import com.mmnaseri.dragonfly.metadata.TableMetadata;
import com.mmnaseri.dragonfly.metadata.TableMetadataAware;
import com.mmnaseri.dragonfly.metadata.TableMetadataRegistry;
import com.mmnaseri.dragonfly.security.DataSecurityManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;

import static com.mmnaseri.couteau.basics.collections.CollectionWrapper.with;

/**
 * This is a caching entity context that will use a generating class enhancer for enhancement
 * purposes.
 *
 * @author Milad Naseri (mmnaseri@programmer.net)
 * @since 1.0 (2013/9/5, 15:24)
 */
public class DefaultEntityContext implements ModifiableEntityContext {

    private static final Log log = LogFactory.getLog(DefaultEntityContext.class);
    private final String key;
    private DataAccess dataAccess;
    private EntityHandlerContext handlerContext;
    private final Map<Class<?>, Map<Class<?>, Class<?>>> interfaces;
    private final DataSecurityManager securityManager;
    private final TableMetadataRegistry tableMetadataRegistry;
    private final Cache<Class<?>, EntityFactory<?>> cache;
    private final DataAccessSession session;
    private ClassLoader defaultClassLoader;

    public DefaultEntityContext(DataSecurityManager securityManager, TableMetadataRegistry tableMetadataRegistry,
            DataAccessSession session) {
        this.securityManager = securityManager;
        this.tableMetadataRegistry = tableMetadataRegistry;
        this.session = session;
        this.key = UUID.randomUUID().toString();
        this.interfaces = new HashMap<Class<?>, Map<Class<?>, Class<?>>>();
        this.cache = new ConcurrentCache<Class<?>, EntityFactory<?>>();
        this.defaultClassLoader = null;
    }

    @Override
    public <E> E getInstance(Class<E> entityType) {
        return getInstance(tableMetadataRegistry.getTableMetadata(entityType));
    }

    @Override
    public <E> E getInstance(TableMetadata<E> tableMetadata) {
        final EntityHandler<E> entityHandler = handlerContext.getHandler(tableMetadata.getEntityType());
        final EntityProxy<E> entityProxy = new EntityProxy<E>(securityManager, tableMetadata, entityHandler,
                dataAccess, session, this);
        if (interfaces.containsKey(tableMetadata.getEntityType())) {
            final Map<Class<?>, Class<?>> classMap = interfaces.get(tableMetadata.getEntityType());
            for (Map.Entry<Class<?>, Class<?>> entry : classMap.entrySet()) {
                entityProxy.addInterface(entry.getKey(), entry.getValue());
            }
        }
        final E entity = enhanceObject(tableMetadata.getEntityType(), entityProxy);
        if (entity instanceof InitializedEntity<?>) {
            //noinspection unchecked
            InitializedEntity<E> initializedEntity = (InitializedEntity<E>) entity;
            initializedEntity.initialize(tableMetadata.getEntityType(), entity, key);
        }
        if (entity instanceof EntityAware) {
            ((EntityAware) entity).setEntity(entity);
        }
        if (entity instanceof EntityHandlerAware<?>) {
            //noinspection unchecked
            ((EntityHandlerAware<E>) entity).setEntityHandler(handlerContext.getHandler(entity));
        }
        if (entity instanceof TableMetadataAware<?>) {
            //noinspection unchecked
            ((TableMetadataAware<E>) entity).setTableMetadata(tableMetadata);
        }
        if (entity instanceof EntityContextAware) {
            ((EntityContextAware) entity).setEntityContext(this);
        }
        return entity;
    }

    private synchronized <E> E enhanceObject(Class<E> type, EntityProxy<E> entityProxy) {
        final EntityFactory<E> factory = getFactory(type, entityProxy);
        return factory.getInstance(entityProxy);
    }

    private <E> EntityFactory<E> getFactory(Class<E> entityType, EntityProxy<E> entityProxy) {
        if (cache.contains(entityType)) {
            //noinspection unchecked
            return (EntityFactory<E>) cache.read(entityType);
        }
        final Class<? extends E> enhancedType = enhanceClass(entityType, entityProxy);
        //        noinspection unchecked
        final EntityFactoryBuilder<E> builder = new EntityFactoryBuilder<E>(entityType, enhancedType);
        final EntityFactory<E> entityFactory = builder.getEntityFactory();
        cache.write(entityType, entityFactory);
        return entityFactory;
    }

    private <E> Class<? extends E> enhanceClass(Class<E> original, EntityProxy<E> entityProxy) {
        final StaticNamingPolicy namingPolicy = new StaticNamingPolicy("Entity");
        final String className = namingPolicy.getClassName(original, null);
        Class<? extends E> enhancedClass = null;
        try {
            enhancedClass = ((Class<?>) ClassUtils.forName(className, original.getClassLoader()))
                    .asSubclass(original);
        } catch (ClassNotFoundException ignored) {
        }
        if (enhancedClass != null) {
            return enhancedClass;
        }
        final ClassEnhancer<E> classEnhancer = new GeneratingClassEnhancer<E>(defaultClassLoader);
        if (defaultClassLoader == null) {
            log.warn(
                    "No class loader has been specified for the entity enhancer. This is likely to cause issues when "
                            + "using data access in a container (such as a web application container)");
        } else {
            final GeneratingClassEnhancer<?> enhancer = (GeneratingClassEnhancer<?>) classEnhancer;
            final DefaultDynamicClassCompiler compiler = new DefaultDynamicClassCompiler(defaultClassLoader);
            final String classPath = getClassPath(defaultClassLoader);
            log.debug("Using classpath " + classPath);
            compiler.setOption(SimpleJavaSourceCompiler.Option.CLASSPATH, classPath);
            enhancer.setCompiler(compiler);
        }
        classEnhancer.setInterfaces(entityProxy.getInterfaces());
        classEnhancer.setSuperClass(original);
        classEnhancer.setNamingPolicy(namingPolicy);
        return classEnhancer.enhance();
    }

    /**
     * This method will determine the classpath argument passed to the JavaC compiler used by this
     * implementation
     *
     * @return the classpath
     */
    protected String getClassPath(ClassLoader classLoader) {
        final Set<File> classPathElements = new HashSet<File>();
        //Adding runtime dependencies available to the project
        final URL[] urls;
        if (classLoader instanceof URLClassLoader) {
            urls = ((URLClassLoader) classLoader).getURLs();
        } else if (classLoader instanceof ConfigurableClassLoader) {
            urls = ((ConfigurableClassLoader) classLoader).getUrls();
        } else {
            urls = new URL[0];
        }
        for (URL url : urls) {
            try {
                final File file = new File(url.toURI());
                if (file.exists()) {
                    classPathElements.add(file);
                }
            } catch (Throwable ignored) {
            }
        }
        return with(classPathElements).transform(new Transformer<File, String>() {
            @Override
            public String map(File input) {
                return input.getAbsolutePath();
            }
        }).join(File.pathSeparator);
    }

    @Override
    public <E> boolean has(E entity) {
        if (entity instanceof InitializedEntity) {
            InitializedEntity initializedEntity = (InitializedEntity) entity;
            return key.equals(initializedEntity.getToken());
        }
        return false;
    }

    @Override
    public void initialize(DataAccess dataAccess) {
        if (isInitialized()) {
            throw new EntityContextReInitializationError();
        }
        this.dataAccess = dataAccess;
    }

    @Override
    public boolean isInitialized() {
        return dataAccess != null;
    }

    @Override
    public void setInterfaces(Map<Class<?>, Map<Class<?>, Class<?>>> interfaces) {
        this.interfaces.clear();
        this.interfaces.putAll(interfaces);
    }

    @Override
    public void setEntityFactories(Map<Class<?>, EntityFactory<?>> factories) {
        for (Map.Entry<Class<?>, EntityFactory<?>> entry : factories.entrySet()) {
            cache.write(entry.getKey(), entry.getValue());
        }
    }

    @Override
    public void setDefaultClassLoader(ClassLoader classLoader) {
        defaultClassLoader = classLoader;
    }

    public void setHandlerContext(EntityHandlerContext handlerContext) {
        this.handlerContext = handlerContext;
    }

    public DataAccess getDataAccess() {
        return dataAccess;
    }

}