org.polymap.model2.engine.InstanceBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.polymap.model2.engine.InstanceBuilder.java

Source

/* 
 * polymap.org
 * Copyright 2012, Falko Brutigam. All rights reserved.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 */
package org.polymap.model2.engine;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import javax.cache.configuration.MutableConfiguration;

import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;

import org.polymap.model2.Association;
import org.polymap.model2.AssociationConcern;
import org.polymap.model2.CollectionProperty;
import org.polymap.model2.CollectionPropertyConcern;
import org.polymap.model2.Composite;
import org.polymap.model2.Computed;
import org.polymap.model2.ComputedProperty;
import org.polymap.model2.Concerns;
import org.polymap.model2.ManyAssociation;
import org.polymap.model2.Property;
import org.polymap.model2.PropertyBase;
import org.polymap.model2.PropertyConcern;
import org.polymap.model2.PropertyConcernBase;
import org.polymap.model2.engine.EntityRepositoryImpl.EntityRuntimeContextImpl;
import org.polymap.model2.engine.LoadingCache.Loader;
import org.polymap.model2.runtime.CompositeInfo;
import org.polymap.model2.runtime.EntityRuntimeContext;
import org.polymap.model2.runtime.ModelRuntimeException;
import org.polymap.model2.runtime.PropertyInfo;
import org.polymap.model2.store.CompositeState;
import org.polymap.model2.store.StoreCollectionProperty;
import org.polymap.model2.store.StoreProperty;

/**
 * 
 *
 * @author <a href="http://www.polymap.de">Falko Brutigam</a>
 */
public final class InstanceBuilder {

    private static Log log = LogFactory.getLog(InstanceBuilder.class);

    protected static Field contextField;

    private static Field concernContextField;

    private static Field concernDelegateField;

    private static Method computedPropertyInitMethod;

    protected static LoadingCache<Field, List<Class>> concerns;

    static {
        try {
            SimpleCacheManager cacheManager = new SimpleCacheManager();
            concerns = LoadingCache.create(cacheManager, new MutableConfiguration());

            contextField = Composite.class.getDeclaredField("context");
            contextField.setAccessible(true);

            concernContextField = PropertyConcernBase.class.getDeclaredField("context");
            concernContextField.setAccessible(true);

            concernDelegateField = PropertyConcernBase.class.getDeclaredField("delegate");
            concernDelegateField.setAccessible(true);

            computedPropertyInitMethod = ComputedProperty.class.getDeclaredMethod("init", PropertyInfo.class,
                    Composite.class);
            computedPropertyInitMethod.setAccessible(true);
        } catch (Exception e) {
            log.error("", e);
            throw new RuntimeException(e);
        }
    }

    // instance *******************************************

    private EntityRuntimeContext context;

    public InstanceBuilder(EntityRuntimeContext context) {
        this.context = context;
    }

    public <T extends Composite> T newComposite(CompositeState state, Class<T> entityClass) {
        try {
            // new instance
            Constructor<?> ctor = entityClass.getConstructor(new Class[] {});
            T instance = (T) ctor.newInstance(new Object[] {});

            // set context
            contextField.set(instance, context);

            //            // init concerns
            //            List<PropertyConcern> concerns = new ArrayList();
            //            Concerns concernsAnnotation = entityClass.getAnnotation( Concerns.class );
            //            if (concernsAnnotation != null) {
            //                for (Class<? extends PropertyConcern> concernClass : concernsAnnotation.value()) {
            //                    concerns.add( concernClass.newInstance() );
            //                }
            //            }

            // init properties
            initProperties(instance, state);

            return instance;
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new ModelRuntimeException("Error while creating an instance of: " + entityClass, e);
        }
    }

    /**
     * Initializes all properties of the given Composite, including all super classes.
     * Composite properties are init with {@link CompositePropertyImpl} which comes back to 
     * {@link InstanceBuilder} when the value is accessed.
     */
    protected void initProperties(Composite instance, CompositeState state) throws Exception {
        //        StoreSPI store = context.getRepository().getStore();
        CompositeInfo compositeInfo = context.getRepository().infoOf(instance.getClass());
        if (compositeInfo == null) {
            log.info("Mixin type not declared on Entity type: " + instance.getClass().getName());
            compositeInfo = new CompositeInfoImpl(instance.getClass());
        }
        assert compositeInfo != null : "No info for Composite type: " + instance.getClass().getName();

        Class superClass = instance.getClass();
        while (superClass != null) {
            // XXX cache fields
            for (Field field : superClass.getDeclaredFields()) {
                if (PropertyBase.class.isAssignableFrom(field.getType())) {
                    field.setAccessible(true);

                    if (field.get(instance) != null) {
                        log.info("Property already inistialized, skipping: " + field.getName());
                        continue;
                    }

                    PropertyInfo info = compositeInfo.getProperty(field.getName());
                    PropertyBase prop = null;

                    // single property
                    if (Property.class.isAssignableFrom(field.getType())) {
                        // Computed
                        if (info.isComputed()) {
                            Computed a = ((PropertyInfoImpl) info).getField().getAnnotation(Computed.class);
                            prop = a.value().newInstance();
                            computedPropertyInitMethod.invoke(prop, info, instance);
                            // always check modifications, default value, immutable, nullable
                            prop = new ConstraintsPropertyInterceptor((Property) prop,
                                    (EntityRuntimeContextImpl) context);
                        } else {
                            StoreProperty storeProp = state.loadProperty(info);
                            // Composite
                            if (Composite.class.isAssignableFrom(info.getType())) {
                                prop = new CompositePropertyImpl(context, storeProp);
                                prop = new ConstraintsPropertyInterceptor((Property) prop,
                                        (EntityRuntimeContextImpl) context);
                            }
                            // primitive type
                            else {
                                prop = new PropertyImpl(storeProp);
                                prop = new ConstraintsPropertyInterceptor((Property) prop,
                                        (EntityRuntimeContextImpl) context);
                            }
                        }
                        // concerns
                        for (PropertyConcernBase concern : fieldConcerns(field, prop)) {
                            prop = concern;
                        }
                    }

                    // Association
                    else if (Association.class.isAssignableFrom(field.getType())) {
                        assert info.isAssociation();
                        // check Computed
                        if (info.isComputed()) {
                            throw new UnsupportedOperationException(
                                    "Computed Association is not supported yet: " + propName(field));
                        }
                        StoreProperty storeProp = state.loadProperty(info);
                        prop = new AssociationImpl(context, storeProp);
                        prop = new ConstraintsAssociationInterceptor((Association) prop,
                                (EntityRuntimeContextImpl) context);
                        // concerns
                        for (PropertyConcernBase concern : fieldConcerns(field, prop)) {
                            prop = concern;
                        }
                    }

                    // ManyAssociation
                    else if (ManyAssociation.class.isAssignableFrom(field.getType())) {
                        assert info.isAssociation();
                        assert info.getMaxOccurs() > 1 : "Field has improper @MaxOccurs: " + propName(field);
                        // check Computed
                        if (info.isComputed()) {
                            throw new UnsupportedOperationException(
                                    "Computed ManyAssociation is not supported yet: " + propName(field));
                        }
                        StoreCollectionProperty storeProp = (StoreCollectionProperty) state.loadProperty(info);
                        prop = new ManyAssociationImpl(context, storeProp);
                        prop = new ConstraintsManyAssociationInterceptor((ManyAssociation) prop,
                                (EntityRuntimeContextImpl) context);
                        // concerns
                        for (PropertyConcernBase concern : fieldConcerns(field, prop)) {
                            prop = concern;
                        }
                    }

                    // Collection
                    else if (CollectionProperty.class.isAssignableFrom(field.getType())) {
                        assert info.getMaxOccurs() > 1 : "Field has improper @MaxOccurs: " + propName(field);
                        StoreCollectionProperty storeProp = (StoreCollectionProperty) state.loadProperty(info);
                        // Composite
                        if (Composite.class.isAssignableFrom(info.getType())) {
                            prop = new CompositeCollectionPropertyImpl(context, storeProp);
                        }
                        // primitive type
                        else {
                            prop = new CollectionPropertyImpl(context, storeProp);
                        }
                        if (info.isNullable()) {
                            throw new ModelRuntimeException("CollectionProperty cannot be @Nullable.");
                        }
                        prop = new ConstraintsCollectionInterceptor((CollectionProperty) prop,
                                (EntityRuntimeContextImpl) context);
                        // concerns
                        for (PropertyConcernBase concern : fieldConcerns(field, prop)) {
                            prop = concern;
                        }
                    }

                    // set field
                    assert prop != null : "Unable to build property instance for: " + field;
                    field.set(instance, prop);
                }
            }
            superClass = superClass.getSuperclass();
        }
    }

    protected String propName(Field field) {
        return field.getDeclaringClass().getSimpleName() + "#" + field.getName();
    }

    protected Iterable<PropertyConcernBase> fieldConcerns(final Field field, final PropertyBase prop)
            throws Exception {
        List<Class> concernTypes = concerns.get(field, new Loader<Field, List<Class>>() {
            public List<Class> load(Field key) {
                List<Class> result = new ArrayList();
                // Class concerns
                Concerns ca = field.getDeclaringClass().getAnnotation(Concerns.class);
                if (ca != null) {
                    result.addAll(Arrays.asList(ca.value()));
                }
                // Field concerns
                Concerns fa = field.getAnnotation(Concerns.class);
                if (fa != null) {
                    for (Class<? extends PropertyConcernBase> concern : fa.value()) {
                        result.add(concern);
                    }
                }
                return result;
            }
        });

        return Iterables.transform(concernTypes, new Function<Class, PropertyConcernBase>() {
            public PropertyConcernBase apply(Class concernType) {
                try {
                    // early check concern type
                    if (Property.class.isAssignableFrom(field.getType())
                            && !PropertyConcern.class.isAssignableFrom(concernType)) {
                        throw new ModelRuntimeException("Concerns of Property have to extend PropertyConcern: "
                                + concernType.getName() + " @ " + field.getName());
                    } else if (CollectionProperty.class.isAssignableFrom(field.getType())
                            && !CollectionPropertyConcern.class.isAssignableFrom(concernType)) {
                        throw new ModelRuntimeException(
                                "Concerns of CollectionProperty have to extend CollectionPropertyConcern: "
                                        + concernType.getName() + " @ " + field.getName());
                    } else if (Association.class.isAssignableFrom(field.getType())
                            && !AssociationConcern.class.isAssignableFrom(concernType)) {
                        throw new ModelRuntimeException(
                                "Concerns of Association have to extend AssociationConcern: "
                                        + concernType.getName() + " @ " + field.getName());
                    }

                    // create concern
                    PropertyConcernBase concern = (PropertyConcernBase) concernType.newInstance();
                    concernContextField.set(concern, context);
                    concernDelegateField.set(concern, prop);

                    return concern;
                } catch (ModelRuntimeException e) {
                    throw e;
                } catch (Exception e) {
                    throw new ModelRuntimeException("Error while initializing concern: " + concernType + " ("
                            + e.getLocalizedMessage() + ")", e);
                }
            }
        });
    }

}