private DatastoreServiceConfig createDatastoreServiceConfigPrototype(
      PersistenceConfiguration persistenceConfiguration, String... timeoutProps) {
    DatastoreServiceConfig datastoreServiceConfig = DatastoreServiceConfig.Builder.withDefaults();

    for (String timeoutProp : timeoutProps) {
      int defaultDeadline = persistenceConfiguration.getIntProperty(timeoutProp);
      if (defaultDeadline > 0) {
        datastoreServiceConfig.deadline(defaultDeadline / 1000d);
      }
    }
    String defaultReadConsistencyStr =
        persistenceConfiguration.getStringProperty(DATASTORE_READ_CONSISTENCY_PROPERTY);
    if (defaultReadConsistencyStr != null) {
      try {
        datastoreServiceConfig.readPolicy(
            new ReadPolicy(Consistency.valueOf(defaultReadConsistencyStr)));
      } catch (IllegalArgumentException iae) {
        throw new NucleusFatalUserException(
            "Illegal value for "
                + DATASTORE_READ_CONSISTENCY_PROPERTY
                + ".  Valid values are "
                + Arrays.toString(Consistency.values()));
      }
    }
    return datastoreServiceConfig;
  }
  /**
   * Construct a DatastoreManager.
   *
   * @param clr The ClassLoaderResolver
   * @param nucContext The NucleusContext
   * @param props Properties to store on this StoreManager
   */
  public DatastoreManager(
      ClassLoaderResolver clr, NucleusContext nucContext, Map<String, Object> props)
      throws NoSuchFieldException, IllegalAccessException {
    super("appengine", clr, nucContext, props);

    // Override some of the default property values for AppEngine
    PersistenceConfiguration conf = nucContext.getPersistenceConfiguration();
    conf.setProperty(
        "datanucleus.attachSameDatastore", Boolean.TRUE.toString()); // Always only one datastore
    // We'd like to respect the user's selection here, but the default value is 1.
    // This is problematic for us in the situation where, for example, an embedded object
    // gets updated more than once in a txn because we end up putting the same entity twice.
    // TODO(maxr) Remove this once we support multiple puts
    conf.setProperty("datanucleus.datastoreTransactionFlushLimit", Integer.MAX_VALUE);
    // Install our key translator
    conf.setProperty("datanucleus.identityKeyTranslatorType", "appengine");

    // Check if datastore api is in CLASSPATH.  Don't let the hard-coded
    // jar name upset you, it's just used for error messages.  The check will
    // succeed so long as the class is available on the classpath
    ClassUtils.assertClassForJarExistsInClasspath(
        clr, "com.google.appengine.api.datastore.DatastoreService", "appengine-api.jar");

    defaultDatastoreServiceConfigPrototypeForReads =
        createDatastoreServiceConfigPrototypeForReads(nucContext.getPersistenceConfiguration());
    defaultDatastoreServiceConfigPrototypeForWrites =
        createDatastoreServiceConfigPrototypeForWrites(nucContext.getPersistenceConfiguration());
    defaultDatastoreTransactionOptionsPrototype =
        createDatastoreTransactionOptionsPrototype(nucContext.getPersistenceConfiguration());

    // Handler for persistence process
    persistenceHandler = new DatastorePersistenceHandler(this);
    flushProcess = new FlushOrdered();

    dba = new DatastoreAdapter();
    initialiseIdentifierFactory(nucContext);

    storageVersion = StorageVersion.fromStoreManager(this);

    String defaultRelationMode = getStringProperty(RELATION_DEFAULT_MODE);
    defaultToOwnedRelations = defaultRelationMode.equalsIgnoreCase("unowned") ? false : true;

    String bigDecimalsEncoding = getStringProperty(BIG_DECIMALS_ENCODEING);
    typeConversionUtils =
        "String".equalsIgnoreCase(bigDecimalsEncoding)
            ? new TypeConversionUtils(true)
            : new TypeConversionUtils(false);

    // Add listener so we can check all metadata for unsupported features and required schema
    metadataValidator = new MetaDataValidator(this, getMetaDataManager(), clr);

    logConfiguration();

    datastoreServiceForReads =
        DatastoreServiceFactoryInternal.getDatastoreService(
            getDefaultDatastoreServiceConfigForReads());
  }
  /**
   * Method to apply any restrictions to the created ResultSet.
   *
   * @param ps The PreparedStatement
   * @param query The query
   * @param applyTimeout Whether to apply the query timeout (if any) direct to the PreparedStatement
   * @throws SQLException Thrown when an error occurs applying the constraints
   */
  public static void prepareStatementForExecution(
      PreparedStatement ps, Query query, boolean applyTimeout) throws SQLException {
    NucleusContext nucleusCtx = query.getExecutionContext().getNucleusContext();
    RDBMSStoreManager storeMgr = (RDBMSStoreManager) query.getStoreManager();
    PersistenceConfiguration conf = nucleusCtx.getPersistenceConfiguration();

    if (applyTimeout) {
      Integer timeout = query.getDatastoreReadTimeoutMillis();
      if (timeout != null && timeout > 0) {
        ps.setQueryTimeout(timeout / 1000);
      }
    }

    // Apply any fetch size
    int fetchSize = 0;
    if (query.getFetchPlan().getFetchSize() > 0) {
      // FetchPlan has a size set so use that
      fetchSize = query.getFetchPlan().getFetchSize();
    }
    if (storeMgr.getDatastoreAdapter().supportsQueryFetchSize(fetchSize)) {
      ps.setFetchSize(fetchSize);
    }

    // Apply any fetch direction
    String fetchDir =
        conf.getStringProperty(RDBMSPropertyNames.PROPERTY_RDBMS_QUERY_FETCH_DIRECTION);
    Object fetchDirExt =
        query.getExtension(RDBMSPropertyNames.PROPERTY_RDBMS_QUERY_FETCH_DIRECTION);
    if (fetchDirExt != null) {
      fetchDir = (String) fetchDirExt;
      if (!fetchDir.equals("forward")
          && !fetchDir.equals("reverse")
          && !fetchDir.equals("unknown")) {
        throw new NucleusUserException(LOCALISER.msg("052512"));
      }
    }

    if (fetchDir.equals("reverse")) {
      ps.setFetchDirection(ResultSet.FETCH_REVERSE);
    } else if (fetchDir.equals("unknown")) {
      ps.setFetchDirection(ResultSet.FETCH_UNKNOWN);
    }

    // Add a limit on the number of rows to include the maximum we may need
    long toExclNo = query.getRangeToExcl();
    if (toExclNo != 0 && toExclNo != Long.MAX_VALUE) {
      if (toExclNo > Integer.MAX_VALUE) {
        // setMaxRows takes an int as input so limit to the correct range
        ps.setMaxRows(Integer.MAX_VALUE);
      } else {
        ps.setMaxRows((int) toExclNo);
      }
    }
  }
 @SuppressWarnings("deprecation")
 private TransactionOptions createDatastoreTransactionOptionsPrototype(
     PersistenceConfiguration persistenceConfig) {
   //    return TransactionOptions.Builder.withXG(
   return TransactionOptions.Builder.allowMultipleEntityGroups(
       persistenceConfig.getBooleanProperty(DATASTORE_ENABLE_XG_TXNS_PROPERTY));
 }