<commit_before>/**********************************************************************
Copyright (c) 2011 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
**********************************************************************/
package com.google.appengine.datanucleus;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.exceptions.NucleusFatalUserException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.ColumnMetaData;
import org.datanucleus.metadata.NullValue;
import org.datanucleus.metadata.Relation;
import org.datanucleus.store.ExecutionContext;
import org.datanucleus.store.ObjectProvider;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Text;
import com.google.appengine.datanucleus.mapping.DatastoreTable;
import com.google.appengine.datanucleus.mapping.InsertMappingConsumer;

/**
 * FieldManager to handle the putting of fields from a managed object into an Entity.
 */
public class StoreFieldManager extends DatastoreFieldManager {
    private static final String PARENT_ALREADY_SET =
        "Cannot set both the primary key and a parent pk field.  If you want the datastore to "
              + "generate an id for you, set the parent pk field to be the value of your parent key "
              + "and leave the primary key field blank.  If you wish to "
              + "provide a named key, leave the parent pk field blank and set the primary key to be a "
              + "Key object made up of both the parent key and the named child.";

    /**
     * Relation types where we want to store child keys on the parent
     */
    private static final Set<Integer> PARENT_RELATION_TYPES =
        Collections.synchronizedSet(Utils.newHashSet(
            Relation.ONE_TO_MANY_BI,
            Relation.ONE_TO_MANY_UNI,
            Relation.ONE_TO_ONE_BI,
            Relation.ONE_TO_ONE_UNI));

    public enum Operation { INSERT, UPDATE, DELETE };

    protected final Operation operation;

    /**
     * @param op ObjectProvider of the object being stored
     * @param storeManager StoreManager for this object
     * @param datastoreEntity The Entity to update with the field values
     * @param fieldNumbers The field numbers being updated in the Entity
     * @param operation Operation being performed
     */
    public StoreFieldManager(ObjectProvider op, DatastoreManager storeManager, Entity datastoreEntity, int[] fieldNumbers,
            Operation operation) {
        super(op, storeManager, datastoreEntity, fieldNumbers);
        this.operation = operation;
    }

    /**
     * @param op ObjectProvider of the object being stored
     * @param storeManager StoreManager for this object
     * @param datastoreEntity The Entity to update with the field values
     * @param operation Operation being performed
     */
    public StoreFieldManager(ObjectProvider op, DatastoreManager storeManager, Entity datastoreEntity, Operation operation) {
        super(op, storeManager, datastoreEntity);
        this.operation = operation;
    }

    /**
     * @param op ObjectProvider of the object being stored
     * @param kind Kind of entity
     * @param storeManager StoreManager for this object
     * @param operation Operation being performed
     */
    public StoreFieldManager(ObjectProvider op, String kind, DatastoreManager storeManager, Operation operation) {
        super(op, kind, storeManager);
        this.operation = operation;
    }

    public void storeBooleanField(int fieldNumber, boolean value) {
      storeObjectField(fieldNumber, value);
    }

    public void storeByteField(int fieldNumber, byte value) {
      storeObjectField(fieldNumber, value);
    }

    public void storeCharField(int fieldNumber, char value) {
      storeObjectField(fieldNumber, value);
    }

    public void storeDoubleField(int fieldNumber, double value) {
      storeObjectField(fieldNumber, value);
    }

    public void storeFloatField(int fieldNumber, float value) {
      storeObjectField(fieldNumber, value);
    }

    public void storeIntField(int fieldNumber, int value) {
      storeObjectField(fieldNumber, value);
    }

    public void storeLongField(int fieldNumber, long value) {
      storeObjectField(fieldNumber, value);
    }

    public void storeShortField(int fieldNumber, short value) {
      storeObjectField(fieldNumber, value);
    }

    public void storeStringField(int fieldNumber, String value) {
      if (isPK(fieldNumber)) {
        storeStringPKField(fieldNumber, value);
      } else if (isParentPK(fieldNumber)) {
        parentMemberMetaData = getMetaData(fieldNumber);
        storeParentStringField(value);
      } else if (isPKNameField(fieldNumber)) {
        storePKNameField(fieldNumber, value);
      } else {
        // could be a JPA "lob" field, in which case we want to store it as Text.
        // DataNucleus sets a cmd with a jdbc type of CLOB if this is the case.
        Object valueToStore = value;
        AbstractMemberMetaData ammd = getMetaData(fieldNumber);
        if (ammd.getColumnMetaData() != null &&
            ammd.getColumnMetaData().length == 1 &&
            "CLOB".equals(ammd.getColumnMetaData()[0].getJdbcType())) {
          valueToStore = new Text(value);
        }
        storeObjectField(fieldNumber, valueToStore);
      }
    }

    public void storeObjectField(int fieldNumber, Object value) {
      if (isPK(fieldNumber)) {
        storePrimaryKey(fieldNumber, value);
      } else if (isParentPK(fieldNumber)) {
        parentMemberMetaData = getMetaData(fieldNumber);
        storeParentField(fieldNumber, value);
      } else if (isPKIdField(fieldNumber)) {
        storePKIdField(fieldNumber, value);
      } else {
        ClassLoaderResolver clr = getClassLoaderResolver();
        AbstractMemberMetaData ammd = getMetaData(fieldNumber);
        if (repersistingForChildKeys && !PARENT_RELATION_TYPES.contains(ammd.getRelationType(clr))) {
          // nothing for us to store
          return;
        }
        if (value != null ) {
          if (ammd.isSerialized()) {
            // If the field is serialized we don't need to apply
            // any conversions before setting it on the entity since
            // the serialization is guaranteed to produce a Blob.
            value = storeManager.getSerializationManager().serialize(clr, ammd, value);
          } else {
            // Perform any conversions from the field type to the stored-type
            value = getConversionUtils().pojoValueToDatastoreValue(clr, value, ammd);
          }
        }

        if (ammd.getEmbeddedMetaData() != null) {
          storeEmbeddedField(ammd, fieldNumber, value);
        } else if ((operation == Operation.INSERT && isInsertable(ammd)) ||
                   (operation == Operation.UPDATE && isUpdatable(ammd))) {
          if (ammd.getRelationType(clr) != Relation.NONE && !ammd.isSerialized()) {
            if (!repersistingForChildKeys) {
              // register a callback for later
              relationFieldManager.storeRelationField(
                  getClassMetaData(), ammd, value, createdWithoutEntity, getInsertMappingConsumer());
            }
            DatastoreTable table = getDatastoreTable();
            if (table != null && table.isParentKeyProvider(ammd)) {
              // a parent key provider is either a many-to-one or the child side of a
              // one-to-one.  Either way we don't want the entity to have a property
              // corresponding to this field because this information is available in
              // the key itself
              return;
            }

            if (!getStoreManager().storageVersionAtLeast(StorageVersion.WRITE_OWNED_CHILD_KEYS_TO_PARENTS)) {
              // don't write child keys to the parent if the storage version isn't high enough
              return;
            }

            // We still want to write the entity property with the keys
            value = extractRelationKeys(value);
          }
          // unwrap SCO values so that the datastore api doesn't honk on unknown types
          value = unwrapSCOField(fieldNumber, value);
          String propName = EntityUtils.getPropertyName(storeManager.getIdentifierFactory(), ammd);
          // validate null values against ammd
          checkNullValue(ammd, propName, value);

          EntityUtils.setEntityProperty(datastoreEntity, ammd, propName, value);
        }
      }
    }

    void storeParentField(int fieldNumber, Object value) {
      if (fieldIsOfTypeKey(fieldNumber)) {
        storeParentKeyPK((Key) value);
      } else {
        throw exceptionForUnexpectedKeyType("Parent primary key", fieldNumber);
      }
    }

    /**
     * We can't trust the fieldNumber on the ammd provided because some embedded
     * fields don't have this set.  That's why we pass it in as a separate param.
     */
    private void storeEmbeddedField(AbstractMemberMetaData ammd, int fieldNumber, Object value) {
      ObjectProvider esm = getEmbeddedObjectProvider(ammd, fieldNumber, value);
      // We need to build a mapping consumer for tObhe embedded class so that we
      // get correct fieldIndex --> metadata mappings for the class in the proper
      // embedded context
      // TODO(maxr) Consider caching this
      InsertMappingConsumer mc = buildMappingConsumer(<commit_msg>Split out into separate method the code to actually manipulate the field value into a datastore value and put it in the entity. Add TODO for wrapping SCO fields<commit_after>/**********************************************************************
Copyright (c) 2011 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
**********************************************************************/
package com.google.appengine.datanucleus;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.exceptions.NucleusFatalUserException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.ColumnMetaData;
import org.datanucleus.metadata.NullValue;
import org.datanucleus.metadata.Relation;
import org.datanucleus.store.ExecutionContext;
import org.datanucleus.store.ObjectProvider;
import org.datanucleus.store.types.sco.SCO;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Text;
import com.google.appengine.datanucleus.mapping.DatastoreTable;
import com.google.appengine.datanucleus.mapping.InsertMappingConsumer;

/**
 * FieldManager to handle the putting of fields from a managed object into an Entity.
 */
public class StoreFieldManager extends DatastoreFieldManager {
    private static final String PARENT_ALREADY_SET =
        "Cannot set both the primary key and a parent pk field.  If you want the datastore to "
              + "generate an id for you, set the parent pk field to be the value of your parent key "
              + "and leave the primary key field blank.  If you wish to "
              + "provide a named key, leave the parent pk field blank and set the primary key to be a "
              + "Key object made up of both the parent key and the named child.";

    /**
     * Relation types where we want to store child keys on the parent
     */
    private static final Set<Integer> PARENT_RELATION_TYPES =
        Collections.synchronizedSet(Utils.newHashSet(
            Relation.ONE_TO_MANY_BI,
            Relation.ONE_TO_MANY_UNI,
            Relation.ONE_TO_ONE_BI,
            Relation.ONE_TO_ONE_UNI));

    public enum Operation { INSERT, UPDATE, DELETE };

    protected final Operation operation;

    /**
     * @param op ObjectProvider of the object being stored
     * @param storeManager StoreManager for this object
     * @param datastoreEntity The Entity to update with the field values
     * @param fieldNumbers The field numbers being updated in the Entity
     * @param operation Operation being performed
     */
    public StoreFieldManager(ObjectProvider op, DatastoreManager storeManager, Entity datastoreEntity, int[] fieldNumbers,
            Operation operation) {
        super(op, storeManager, datastoreEntity, fieldNumbers);
        this.operation = operation;
    }

    /**
     * @param op ObjectProvider of the object being stored
     * @param storeManager StoreManager for this object
     * @param datastoreEntity The Entity to update with the field values
     * @param operation Operation being performed
     */
    public StoreFieldManager(ObjectProvider op, DatastoreManager storeManager, Entity datastoreEntity, Operation operation) {
        super(op, storeManager, datastoreEntity);
        this.operation = operation;
    }

    /**
     * @param op ObjectProvider of the object being stored
     * @param kind Kind of entity
     * @param storeManager StoreManager for this object
     * @param operation Operation being performed
     */
    public StoreFieldManager(ObjectProvider op, String kind, DatastoreManager storeManager, Operation operation) {
        super(op, kind, storeManager);
        this.operation = operation;
    }

    public void storeBooleanField(int fieldNumber, boolean value) {
      storeFieldInEntity(fieldNumber, value);
    }

    public void storeByteField(int fieldNumber, byte value) {
      storeFieldInEntity(fieldNumber, value);
    }

    public void storeCharField(int fieldNumber, char value) {
      storeFieldInEntity(fieldNumber, value);
    }

    public void storeDoubleField(int fieldNumber, double value) {
      storeFieldInEntity(fieldNumber, value);
    }

    public void storeFloatField(int fieldNumber, float value) {
      storeFieldInEntity(fieldNumber, value);
    }

    public void storeIntField(int fieldNumber, int value) {
      storeFieldInEntity(fieldNumber, value);
    }

    public void storeLongField(int fieldNumber, long value) {
      storeFieldInEntity(fieldNumber, value);
    }

    public void storeShortField(int fieldNumber, short value) {
      storeFieldInEntity(fieldNumber, value);
    }

    public void storeStringField(int fieldNumber, String value) {
      if (isPK(fieldNumber)) {
        storeStringPKField(fieldNumber, value);
      } else if (isParentPK(fieldNumber)) {
        parentMemberMetaData = getMetaData(fieldNumber);
        storeParentStringField(value);
      } else if (isPKNameField(fieldNumber)) {
        storePKNameField(fieldNumber, value);
      } else {
        // could be a JPA "lob" field, in which case we want to store it as Text.
        // DataNucleus sets a cmd with a jdbc type of CLOB if this is the case.
        Object valueToStore = value;
        AbstractMemberMetaData ammd = getMetaData(fieldNumber);
        if (ammd.getColumnMetaData() != null &&
            ammd.getColumnMetaData().length == 1 &&
            "CLOB".equals(ammd.getColumnMetaData()[0].getJdbcType())) {
          valueToStore = new Text(value);
        }

        storeFieldInEntity(fieldNumber, valueToStore);
      }
    }

    public void storeObjectField(int fieldNumber, Object value) {
      if (isPK(fieldNumber)) {
        storePrimaryKey(fieldNumber, value);
      } else if (isParentPK(fieldNumber)) {
        parentMemberMetaData = getMetaData(fieldNumber);
        storeParentField(fieldNumber, value);
      } else if (isPKIdField(fieldNumber)) {
        storePKIdField(fieldNumber, value);
      } else {
        ClassLoaderResolver clr = getClassLoaderResolver();
        AbstractMemberMetaData ammd = getMetaData(fieldNumber);
        if (repersistingForChildKeys && !PARENT_RELATION_TYPES.contains(ammd.getRelationType(clr))) {
          // nothing for us to store
          return;
        }

        storeFieldInEntity(fieldNumber, value);

        if (!(value instanceof SCO)) {
        // TODO Wrap SCO fields. Currently this causes some tests to fail due to the fragility of the design of this plugin
//        getObjectProvider().wrapSCOField(fieldNumber, value, false, false, true);
        }
      }
    }

    /**
     * Method to store the provided value in the Entity for the specified field.
     * @param fieldNumber The absolute field number
     * @param value Value to store (or rather to manipulate into a suitable form for the datastore).
     * @return Whether it stored the value (doesn't mean an error, just that wasn't needed to be stored)
     */
    private boolean storeFieldInEntity(int fieldNumber, Object value) {
      ClassLoaderResolver clr = getClassLoaderResolver();
      AbstractMemberMetaData ammd = getMetaData(fieldNumber);
      if (value != null ) {
        if (ammd.isSerialized()) {
          // If the field is serialized we don't need to apply any conversions before setting it on the 
          // entity since the serialization is guaranteed to produce a Blob.
          value = storeManager.getSerializationManager().serialize(clr, ammd, value);
        } else {
          // Perform any conversions from the field type to the stored-type
          value = getConversionUtils().pojoValueToDatastoreValue(clr, value, ammd);
        }
      }

      if (ammd.getEmbeddedMetaData() != null) {
        storeEmbeddedField(ammd, fieldNumber, value);
      }
      else if ((operation == Operation.INSERT && isInsertable(ammd)) ||
               (operation == Operation.UPDATE && isUpdatable(ammd))) {
        if (ammd.getRelationType(clr) != Relation.NONE && !ammd.isSerialized()) {
          if (!repersistingForChildKeys) {
            // register a callback for later
            relationFieldManager.storeRelationField(
                getClassMetaData(), ammd, value, createdWithoutEntity, getInsertMappingConsumer());
          }
          DatastoreTable table = getDatastoreTable();
          if (table != null && table.isParentKeyProvider(ammd)) {
            // a parent key provider is either a many-to-one or the child side of a one-to-one.  
            // Either way we don't want the entity to have a property corresponding to this field 
            // because this information is available in the key itself
            return false;
          }

          if (!getStoreManager().storageVersionAtLeast(StorageVersion.WRITE_OWNED_CHILD_KEYS_TO_PARENTS)) {
            // don't write child keys to the parent if the storage version isn't high enough
            return false;
          }

          // We still want to write the entity property with the keys
          value = extractRelationKeys(value);
        }

        // unwrap SCO values so that the datastore api doesn't honk on unknown types
        if (value instanceof SCO) {
          value = unwrapSCOField(fieldNumber, value);
        }

        // Set the property on the entity, allowing for null rules
        String propName = EntityUtils.getPropertyName(storeManager.getIdentifierFactory(), ammd);
        checkNullValue(ammd, propName, value);
        EntityUtils.setEntityProperty(datastoreEntity, ammd, propName, value);
        return true;
      }
      return false;
    }

    void storeParentField(int fieldNumber, Object value) {
      if (fieldIsOfTypeKey(fieldNumber)) {
        storeParentKeyPK((Key) value);
      } else {
        throw exceptionForUnexpectedKeyType("Parent primary key", fieldNumber);
      }
    }

    /**
     * We can't trust the fieldNumber on the ammd provided because some embedded
     * fields don't have this set.  That's why we pass it in as a separate param.
     */
    private void storeEmbeddedField(AbstractMemberMetaData ammd, int fieldNumber, Object value) {
      ObjectProvider esm = getEmbeddedObjectProvider(ammd, fieldNumber, value);
      // We need to build a mapping consumer for tObhe embedded class so that we
      // get correct fieldIndex --> metadata mappings for the class in the proper
      // embedded context
      // TODO(maxr) Consider caching this
      InsertMappingConsumer mc = buildMappingConsumer(
{'fraction_non_alphanumeric': 0.08779623064004478, 'fraction_numerical': 0.000933009889904833, 'mean_word_length': 3.0295112781954887, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1691196', 'n_tokens_mistral': 5647, 'n_tokens_neox': 5508, 'n_words': 1838}