/src/com/google/appengine/datanucleus/query/ProjectionResultTransformer.java

http://datanucleus-appengine.googlecode.com/ · Java · 208 lines · 135 code · 20 blank · 53 comment · 34 complexity · 7e5693f8db74e6c56134cdb6b34175c4 MD5 · raw file

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