/src/com/google/appengine/datanucleus/query/ProjectionResultTransformer.java
Java | 208 lines | 135 code | 20 blank | 53 comment | 34 complexity | 7e5693f8db74e6c56134cdb6b34175c4 MD5 | raw file
1/********************************************************************** 2Copyright (c) 2009 Google Inc. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15**********************************************************************/ 16package com.google.appengine.datanucleus.query; 17 18import com.google.appengine.api.datastore.Entity; 19 20import org.datanucleus.ClassLoaderResolver; 21import org.datanucleus.exceptions.NucleusUserException; 22import org.datanucleus.metadata.AbstractMemberMetaData; 23 24import com.google.appengine.datanucleus.DatastoreManager; 25import com.google.appengine.datanucleus.Utils; 26import com.google.appengine.datanucleus.mapping.DatastoreTable; 27 28import org.datanucleus.query.QueryUtils; 29import org.datanucleus.store.ExecutionContext; 30import org.datanucleus.store.ObjectProvider; 31import org.datanucleus.store.mapped.mapping.EmbeddedMapping; 32import org.datanucleus.store.mapped.mapping.JavaTypeMapping; 33import org.datanucleus.util.Localiser; 34import org.datanucleus.util.NucleusLogger; 35 36import java.lang.reflect.Field; 37import java.util.Arrays; 38import java.util.HashMap; 39import java.util.List; 40import java.util.Map; 41 42import javax.jdo.spi.PersistenceCapable; 43 44/** 45 * Wraps the provided entity-to-pojo {@link Utils.Function} in a {@link Utils.Function} 46 * that extracts the fields identified by the provided field meta-data. 47 * @author Max Ross <maxr@google.com> 48 */ 49class ProjectionResultTransformer implements Utils.Function<Entity, Object> { 50 51 protected static final Localiser LOCALISER=Localiser.getInstance( 52 "org.datanucleus.Localisation", org.datanucleus.ClassConstants.NUCLEUS_CONTEXT_LOADER); 53 54 private final Utils.Function<Entity, Object> entityToPojoFunc; 55 private final ExecutionContext ec; 56 private final List<String> projectionFields; 57 private final List<String> projectionAliases; 58 private final String candidateAlias; 59 private final Class resultClass; 60 private final Map<String, Field> resultClassFieldsByName = new HashMap(); 61 62 ProjectionResultTransformer(Utils.Function<Entity, Object> entityToPojoFunc, ExecutionContext ec, 63 String candidateAlias, Class resultClass, List<String> projectionFields, List<String> projectionAliases) { 64 this.entityToPojoFunc = entityToPojoFunc; 65 this.ec = ec; 66 this.projectionFields = projectionFields; 67 this.projectionAliases = projectionAliases; 68 this.candidateAlias = candidateAlias; 69 this.resultClass = resultClass; 70 if (resultClass != null && !QueryUtils.resultClassIsSimple(resultClass.getName())) { 71 populateDeclaredFieldsForUserType(resultClass, resultClassFieldsByName); 72 } 73 } 74 75 /** 76 * Method to convert the Entity for this row into the required query result. 77 * Processes any result expression(s), and any result class. 78 * @param from The entity 79 * @return The required result format, for this Entity 80 */ 81 public Object apply(Entity from) { 82 PersistenceCapable pc = (PersistenceCapable) entityToPojoFunc.apply(from); 83 ObjectProvider op = ec.findObjectProvider(pc); 84 List<Object> values = Utils.newArrayList(); 85 86 // Need to fetch the fields one at a time instead of using 87 // sm.provideFields() because that method doesn't respect the ordering 88 // of the field numbers and that ordering is important here. 89 for (String projectionField : projectionFields) { 90 ObjectProvider currentOP = op; 91 DatastoreManager storeMgr = (DatastoreManager) ec.getStoreManager(); 92 ClassLoaderResolver clr = ec.getClassLoaderResolver(); 93 List<String> fieldNames = getTuples(projectionField, candidateAlias); 94 JavaTypeMapping typeMapping; 95 Object curValue = null; 96 boolean shouldBeDone = false; 97 for (String fieldName : fieldNames) { 98 if (shouldBeDone) { 99 throw new RuntimeException( 100 "Unable to extract field " + projectionField + " from " + 101 op.getClassMetaData().getFullClassName() + ". This is most likely an App Engine bug."); 102 } 103 DatastoreTable table = storeMgr.getDatastoreClass( 104 currentOP.getClassMetaData().getFullClassName(), clr); 105 typeMapping = table.getMappingForSimpleFieldName(fieldName); 106 if (typeMapping instanceof EmbeddedMapping) { 107 // reset the mapping to be the mapping for the next embedded field 108 typeMapping = table.getMappingForSimpleFieldName(fieldName); 109 } else { 110 // The first non-embedded mapping should be for the field 111 // with the value we ultimately want to return. 112 // If we still have more tuples then that's an error. 113 shouldBeDone = true; 114 } 115 AbstractMemberMetaData curMemberMetaData = typeMapping.getMemberMetaData(); 116 curValue = currentOP.provideField(curMemberMetaData.getAbsoluteFieldNumber()); 117 if (curValue == null) { 118 // If we hit a null value we're done even if we haven't consumed 119 // all the tuple fields 120 break; 121 } 122 currentOP = ec.findObjectProvider(curValue); 123 } 124 // the value we have left at the end is the embedded field value we want to return 125 values.add(curValue); 126 } 127 128 if (resultClass != null) { 129 // Convert field values into result object 130 Object[] valueArray = values.toArray(new Object[values.size()]); 131 return getResultObjectForValues(valueArray); 132 } 133 134 if (values.size() == 1) { 135 // If there's only one value, just return it. 136 return values.get(0); 137 } 138 // Return an Object array. 139 return values.toArray(new Object[values.size()]); 140 } 141 142 private Object getResultObjectForValues(Object[] values) { 143 144 if (QueryUtils.resultClassIsSimple(resultClass.getName())) { 145 // User wants a single field 146 if (values.length == 1 && (values[0] == null || resultClass.isAssignableFrom(values[0].getClass()))) { 147 // Simple object is the correct type so just give them the field 148 return values[0]; 149 } 150 else if (values.length == 1 && !resultClass.isAssignableFrom(values[0].getClass())) { 151 // Simple object is not assignable to the ResultClass so throw an error 152 String msg = LOCALISER.msg("021202", 153 resultClass.getName(), values[0].getClass().getName()); 154 NucleusLogger.QUERY.error(msg); 155 throw new NucleusUserException(msg); 156 } 157 else { 158 throw new NucleusUserException("Result class is simple, but field value " + values + " not convertible into that"); 159 } 160 } else { 161 // User requires creation of one of his own type of objects, or a Map 162 // A. Find a constructor with the correct constructor arguments 163 Class[] resultFieldTypes = new Class[values.length]; 164 for (int i=0;i<values.length;i++) { 165 resultFieldTypes[i] = values[i].getClass(); // TODO Cater for null (need passing in from field info) 166 } 167 Object obj = QueryUtils.createResultObjectUsingArgumentedConstructor(resultClass, values, resultFieldTypes); 168 if (obj != null) { 169 return obj; 170 } 171 172 // B. No argumented constructor exists so create an object and update fields using fields/put method/set method 173 String[] resultFieldNames = projectionAliases.toArray(new String[projectionAliases.size()]); 174 obj = QueryUtils.createResultObjectUsingDefaultConstructorAndSetters(resultClass, resultFieldNames, 175 resultClassFieldsByName, values); 176 177 return obj; 178 } 179 } 180 181 /** 182 * Populate a map with the declared fields of the result class and super classes. 183 * @param cls the class to find the declared fields and populate the map 184 */ 185 private void populateDeclaredFieldsForUserType(Class cls, Map resultClassFieldsByName) 186 { 187 for (int i=0;i<cls.getDeclaredFields().length;i++) 188 { 189 if (resultClassFieldsByName.put(cls.getDeclaredFields()[i].getName().toUpperCase(), cls.getDeclaredFields()[i]) != null) 190 { 191 throw new NucleusUserException(LOCALISER.msg("021210", cls.getDeclaredFields()[i].getName())); 192 } 193 } 194 if (cls.getSuperclass() != null) 195 { 196 populateDeclaredFieldsForUserType(cls.getSuperclass(), resultClassFieldsByName); 197 } 198 } 199 200 /** 201 * Turns "a.b.c into a List containing "a", "b", and "c" 202 */ 203 private static List<String> getTuples(String dotDelimitedFieldName, String alias) { 204 // split takes a regex so need to escape the period character 205 List<String> tuples = Arrays.asList(dotDelimitedFieldName.split("\\.")); 206 return DatastoreQuery.getTuples(tuples, alias); 207 } 208}