All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avaje.ebeaninternal.server.query.SqlTreeNodeBean Maven / Gradle / Ivy

There is a newer version: 2.8.1
Show newest version
/**
 * Copyright (C) 2006  Robin Bygrave
 * 
 * This file is part of Ebean.
 * 
 * Ebean 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.
 *  
 * Ebean 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.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Ebean; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA  
 */
package com.avaje.ebeaninternal.server.query;

import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.api.SpiQuery.Mode;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.DbReadContext;
import com.avaje.ebeaninternal.server.deploy.DbSqlContext;
import com.avaje.ebeaninternal.server.deploy.InheritInfo;
import com.avaje.ebeaninternal.server.deploy.TableJoin;
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
import com.avaje.ebeaninternal.server.lib.util.StringHelper;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Normal bean included in the query.
 */
public class SqlTreeNodeBean implements SqlTreeNode {

	private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0];

	final BeanDescriptor desc;
		
	final IdBinder idBinder;

	/**
	 * The children which will be other SelectBean or SelectProxyBean.
	 */
	final SqlTreeNode[] children;

	final boolean readOnlyLeaf;
	
	/**
	 * Set to true if this is a partial object fetch.
	 */
	final boolean partialObject;
	
	/**
	 * The set of properties explicitly included in the query.
	 * We actually add the manyProp names to this as they are
	 * references/proxies we add via createListProxies().
	 */
	final Set partialProps;
	
	/**
	 * The hash of the partialProps (calculate once).
	 */
	final int partialHash;
	
	final BeanProperty[] properties;
	
	/**
	 * Extra where clause added by Where annotation on associated many.
	 */
	final String extraWhere;
	
	final BeanPropertyAssoc nodeBeanProp;

	final TableJoin[] tableJoins;

	/**
	 * False if report bean and has no id property.
	 */
	final boolean readId;
	
	final boolean disableLazyLoad;
	
	final InheritInfo inheritInfo;
	
	final String prefix;
	
	final Set includedProps;

	final Map pathMap;
	
	public SqlTreeNodeBean(String prefix, BeanPropertyAssoc beanProp, 
			SqlTreeProperties props, List myChildren, boolean withId) {
		
		this(prefix, beanProp, beanProp.getTargetDescriptor(),props, myChildren, withId);
	}
	
	/**
	 * Create with the appropriate node.
	 */
	public SqlTreeNodeBean(String prefix, BeanPropertyAssoc beanProp, BeanDescriptor desc, 
			SqlTreeProperties props, List myChildren, boolean withId) {

		this.prefix = prefix;
		this.nodeBeanProp = beanProp;
		this.desc = desc;
		this.inheritInfo = desc.getInheritInfo();
		this.extraWhere = (beanProp == null) ? null : beanProp.getExtraWhere();
		
		this.idBinder = desc.getIdBinder();
		
		// the bean has an Id property and we want to use it
		this.readId = withId && (desc.propertiesId().length > 0);
		this.disableLazyLoad = !readId || desc.isSqlSelectBased();
		
		this.tableJoins = props.getTableJoins();
		
		this.partialObject = props.isPartialObject();
		this.partialProps = props.getIncludedProperties();
		this.partialHash = partialObject ? partialProps.hashCode() : 0;
		
		this.readOnlyLeaf = props.isReadOnly();
		
		this.properties = props.getProps();
		
		if (partialObject){
			// merge the explicit partialProps with the implicitly added
			// list proxies (that are added by createListProxies()) to get
			// the full set of 'loaded' properties for this bean.
			includedProps = LoadedPropertiesCache.get(partialHash, partialProps, desc);
		} else {
			includedProps = null;
		}
		
		if (myChildren == null) {
			children = NO_CHILDREN;
		} else {
			children = myChildren.toArray(new SqlTreeNode[myChildren.size()]);
		}
		
		pathMap = createPathMap(prefix, desc);
	}
	
	private Map createPathMap(String prefix, BeanDescriptor desc) {
		
		BeanPropertyAssocMany[] manys = desc.propertiesMany();
	
		HashMap m = new HashMap();
		for (int i = 0; i < manys.length; i++) {
			String name = manys[i].getName();
			m.put(name, getPath(prefix, name));
		}
		
		return m;
	}
	
	private String getPath(String prefix, String propertyName){
		if (prefix == null){
			return propertyName;
		} else {
			return prefix+"."+propertyName;
		}
	}

	protected void postLoad(DbReadContext cquery, Object loadedBean, Object id) {
	}

	public void buildSelectExpressionChain(List selectChain){
	       if (readId){
	            idBinder.buildSelectExpressionChain(prefix, selectChain);
	       }
           for (int i = 0, x = properties.length; i < x; i++) {
               properties[i].buildSelectExpressionChain(prefix, selectChain);
           }
           // recursively continue reading...
           for (int i = 0; i < children.length; i++) {
               // read each child... and let them set their
               // values back to this localBean
               children[i].buildSelectExpressionChain(selectChain);
           }
	}
	
	/**
	 * read the properties from the resultSet.
	 */
	public void load(DbReadContext ctx, Object parentBean, int parentState) throws SQLException {

		// bean already existing in the persistence context
		Object contextBean = null;
		
		Class localType;
		BeanDescriptor localDesc;
		IdBinder localIdBinder;
		Object localBean;
		
		if (inheritInfo != null){
			InheritInfo localInfo = inheritInfo.readType(ctx);
			if (localInfo == null){
				// the bean must be null
				localIdBinder = idBinder;
				localBean = null;
				localType = null;
				localDesc = desc;
			} else {
				localBean = localInfo.createBean(ctx.isVanillaMode());
				localType = localInfo.getType();
				localIdBinder = localInfo.getIdBinder();
				localDesc = localInfo.getBeanDescriptor();
			}
	        
		} else {
			localType = null;
			localDesc = desc;
			localBean = desc.createBean(ctx.isVanillaMode());
			localIdBinder = idBinder;
		}
		
		Mode queryMode = ctx.getQueryMode();
		
		PersistenceContext persistenceContext = ctx.getPersistenceContext();
		
		Object id = null;
		if (!readId){
			// report type bean... or perhaps excluding the id for SqlSelect?
			
		} else {
			id = localIdBinder.readSet(ctx, localBean);
			if (id == null){
				// bean must be null...
				localBean = null;
			} else {
				// check the PersistenceContext to see if the bean already exists
				contextBean = persistenceContext.putIfAbsent(id, localBean);
				if (contextBean == null){
                    // bean just added to the persistenceContext
                    contextBean = localBean;                    				    
				} else {
					// bean already exists in persistenceContext
					if (queryMode.isLoadContextBean()){
						// refresh it anyway (lazy loading for example)
						localBean = contextBean;
						if (localBean instanceof EntityBean){
						    // temporarily turn off interception during load
						    ((EntityBean)localBean)._ebean_getIntercept().setIntercepting(false);
						}
					} else {
					    // ignore the DB data...
						localBean = null;	
					}
				}
			}
		} 

		ctx.setCurrentPrefix(prefix, pathMap);
		
		ctx.propagateState(localBean);
		
		SqlBeanLoad sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode);
		
		if (inheritInfo == null){
			// normal behaviour with no inheritance
			for (int i = 0, x = properties.length; i < x; i++) {
				properties[i].load(sqlBeanLoad);
			}
			
		} else {
			// take account of inheritance and due to subclassing approach
			// need to get a 'local' version of the property
			for (int i = 0, x = properties.length; i < x; i++) {
				// get a local version of the BeanProperty
				BeanProperty p = localDesc.getBeanProperty(properties[i].getName());
				if (p != null){
					p.load(sqlBeanLoad);
				} else {
                    properties[i].loadIgnore(ctx);
				}
			}	
		} 
		
		for (int i = 0, x = tableJoins.length; i < x; i++) {
			tableJoins[i].load(sqlBeanLoad);
		}

		boolean lazyLoadMany = false;
		if (localBean == null && queryMode.equals(Mode.LAZYLOAD_MANY)){
			// batch lazy load many into existing contextBean
			localBean = contextBean;
			lazyLoadMany = true;
		}
		
		// recursively continue reading...
		for (int i = 0; i < children.length; i++) {
			// read each child... and let them set their
			// values back to this localBean
			children[i].load(ctx, localBean, parentState);
		}

		if (lazyLoadMany){
			// special case where we load children 
			
		} else if (localBean != null) {
			
			ctx.setCurrentPrefix(prefix, pathMap);
			if (!ctx.isVanillaMode()){
			    // only create lazy loading collection proxies
			    // when not in vanilla mode
                createListProxies(localDesc, ctx, localBean);
			}
			
			localDesc.postLoad(localBean, includedProps);

            if (localBean instanceof EntityBean) {
                EntityBeanIntercept ebi = ((EntityBean)localBean)._ebean_getIntercept();
                ebi.setPersistenceContext(persistenceContext);
                ebi.setLoadedProps(includedProps);
                if (queryMode.isLoadContextBean()){
                    // finished Lazy Load or Refresh so we 
                    // don't reset the dirty state
                    ebi.setLoadedLazy();
                } else {
                    // normal bean loading
                    ebi.setLoaded();                    
                }

                if (partialObject) {
                    ctx.register(null, ebi);
                }

                if (disableLazyLoad) {
                    // bean does not have an Id or is SqlSelect based
                    ebi.setDisableLazyLoad(true);
                }
                if (ctx.isAutoFetchProfiling()) {
                    // collect autofetch profiling for this bean...
                    ctx.profileBean(ebi, prefix);
                }
            }
			
		}
		if (parentBean != null && contextBean != null) {
			// set this back to the parentBean
			nodeBeanProp.setValue(parentBean, contextBean);
		}

		if (!readId){
			// a bean with no Id (never found in context)
			postLoad(ctx, localBean, id);
			
		} else {
			// return the contextBean which is either the localBean
			// read from the resultSet and put into the context OR
			// the 'matching' bean that already existed in the context
			postLoad(ctx, contextBean, id);
		}
	}

	/**
	 * Create lazy loading proxies for the Many's except for the one that is
	 * included in the actual query.
	 */
	private void createListProxies(BeanDescriptor localDesc, DbReadContext ctx, Object localBean) { 
		
		BeanPropertyAssocMany fetchedMany = ctx.getManyProperty();

		// load the List/Set/Map proxy objects (deferred fetching of lists)
		BeanPropertyAssocMany[] manys = localDesc.propertiesMany();
		for (int i = 0; i < manys.length; i++) {

			if (fetchedMany != null && fetchedMany.equals(manys[i])) {
				// this many property is included in the query...
				// it is being loaded with real row data (result[1])
			} else {
				// create a proxy for the many (deferred fetching)
				BeanCollection ref = manys[i].createReferenceIfNull(localBean);
				if (ref != null){
				    ctx.register(manys[i].getName(), ref);
				}
			}
		}
	}

	/**
	 * Append the property columns to the buffer.
	 */
	public void appendSelect(DbSqlContext ctx, boolean subQuery) {
		
		ctx.pushJoin(prefix);
		ctx.pushTableAlias(prefix);

		if (nodeBeanProp != null) {
			ctx.append(NEW_LINE).append("        ");
		}
				
		if (!subQuery && inheritInfo != null){
			ctx.appendColumn(inheritInfo.getDiscriminatorColumn());
		}
		
		if (readId) {
			appendSelect(ctx, false, idBinder.getProperties());
		}
		appendSelect(ctx, subQuery, properties);
		appendSelectTableJoins(ctx);

		for (int i = 0; i < children.length; i++) {
			// read each child... and let them set their
			// values back to this localBean
			children[i].appendSelect(ctx, subQuery);
		}
		
		ctx.popTableAlias();
		ctx.popJoin();
	}

	private void appendSelectTableJoins(DbSqlContext ctx) {

		String baseAlias = ctx.getTableAlias(prefix);
		
		for (int i = 0; i < tableJoins.length; i++) {
			TableJoin join = tableJoins[i];

			String alias = baseAlias+i;

			ctx.pushSecondaryTableAlias(alias);
			join.appendSelect(ctx, false);
			ctx.popTableAlias();
		}
	}

	/**
	 * Append the properties to the buffer.
	 */
	private void appendSelect(DbSqlContext ctx, boolean subQuery, BeanProperty[] props) {

		for (int i = 0; i < props.length; i++) {
			props[i].appendSelect(ctx, subQuery);
		}
	}
	
	
	public void appendWhere(DbSqlContext ctx) {

		if (inheritInfo != null) {
			if (inheritInfo.isRoot()) {
				// at root of hierarchy so don't bother
				// adding a where clause because we want
				// all the types...
			} else {
				// restrict to this type and 
				// sub types of this type.
				if (ctx.length() > 0){
					ctx.append(" and");
				} 
				ctx.append(" ").append(ctx.getTableAlias(prefix)).append(".");//tableAlias
				ctx.append(inheritInfo.getWhere()).append(" ");
			}
		}
		if (extraWhere != null){
			if (ctx.length() > 0){
				ctx.append(" and");
			} 
			String ta = ctx.getTableAlias(prefix);
			String ew = StringHelper.replaceString(extraWhere, "${ta}", ta);
			ctx.append(" ").append(ew).append(" ");
		}
		
		for (int i = 0; i < children.length; i++) {
			// recursively add to the where clause any
			// fixed predicates (extraWhere etc)
			children[i].appendWhere(ctx);
		}
	}
	
	/**
	 * Append to the FROM clause for this node.
	 */
	public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {

		ctx.pushJoin(prefix);
		ctx.pushTableAlias(prefix);

		forceOuterJoin = appendFromBaseTable(ctx, forceOuterJoin);
		
		for (int i = 0; i < properties.length; i++) {
			// usually nothing... except for 1-1 Exported
			properties[i].appendFrom(ctx, forceOuterJoin);
		}
		
		for (int i = 0; i < children.length; i++) {
			children[i].appendFrom(ctx, forceOuterJoin);
		}
		
		ctx.popTableAlias();
		ctx.popJoin();
	}
	
	/**
	 * Join to base table for this node. This includes a join to
	 * the intersection table if this is a ManyToMany node.
	 */
	public boolean appendFromBaseTable(DbSqlContext ctx, boolean forceOuterJoin) {
				
		if (nodeBeanProp instanceof BeanPropertyAssocMany){
			BeanPropertyAssocMany manyProp = (BeanPropertyAssocMany)nodeBeanProp;
			if (manyProp.isManyToMany()){
				
				String alias = ctx.getTableAlias(prefix);
				String[] split = SplitName.split(prefix);
				String parentAlias = ctx.getTableAlias(split[0]);
				String alias2 = alias+"z_";
				
				TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin();
				manyToManyJoin.addJoin(forceOuterJoin, parentAlias, alias2, ctx);
				
				return nodeBeanProp.addJoin(forceOuterJoin, alias2, alias, ctx);
			}
			
		}
        
		return nodeBeanProp.addJoin(forceOuterJoin, prefix, ctx);
	}

    
	/**
	 * Summary description.
	 */
	public String toString() {
		return "SqlTreeNodeBean: " + desc;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy