PageRenderTime 27ms CodeModel.GetById 14ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 0ms

/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/**********************************************************************
  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}