/src/com/google/appengine/datanucleus/DatastoreManager.java

http://datanucleus-appengine.googlecode.com/ · Java · 711 lines · 408 code · 89 blank · 214 comment · 43 complexity · 6d741ab7e3c12429441561bcf6bce138 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;
  14. import com.google.appengine.api.datastore.DatastoreService;
  15. import com.google.appengine.api.datastore.DatastoreServiceConfig;
  16. import com.google.appengine.api.datastore.ReadPolicy;
  17. import com.google.appengine.api.datastore.ReadPolicy.Consistency;
  18. import com.google.appengine.api.datastore.TransactionOptions;
  19. import com.google.appengine.datanucleus.mapping.DatastoreTable;
  20. import com.google.appengine.datanucleus.scostore.FKListStore;
  21. import com.google.appengine.datanucleus.scostore.FKSetStore;
  22. import org.datanucleus.ClassLoaderResolver;
  23. import org.datanucleus.flush.FlushOrdered;
  24. import org.datanucleus.NucleusContext;
  25. import org.datanucleus.PersistenceConfiguration;
  26. import org.datanucleus.PropertyNames;
  27. import org.datanucleus.api.ApiAdapter;
  28. import org.datanucleus.exceptions.NucleusFatalUserException;
  29. import org.datanucleus.metadata.AbstractClassMetaData;
  30. import org.datanucleus.metadata.AbstractMemberMetaData;
  31. import org.datanucleus.metadata.ClassMetaData;
  32. import org.datanucleus.metadata.InheritanceStrategy;
  33. import org.datanucleus.metadata.RelationType;
  34. import org.datanucleus.store.DefaultCandidateExtent;
  35. import org.datanucleus.ExecutionContext;
  36. import org.datanucleus.store.Extent;
  37. import org.datanucleus.store.NucleusConnection;
  38. import org.datanucleus.store.NucleusConnectionImpl;
  39. import org.datanucleus.state.ObjectProvider;
  40. import org.datanucleus.store.StoreData;
  41. import org.datanucleus.store.connection.ConnectionFactory;
  42. import org.datanucleus.store.connection.ManagedConnection;
  43. import org.datanucleus.store.exceptions.NoExtentException;
  44. import org.datanucleus.store.mapped.exceptions.NoTableManagedException;
  45. import org.datanucleus.store.fieldmanager.FieldManager;
  46. import org.datanucleus.store.mapped.DatastoreContainerObject;
  47. import org.datanucleus.store.mapped.MappedStoreData;
  48. import org.datanucleus.store.mapped.MappedStoreManager;
  49. import org.datanucleus.store.mapped.StatementClassMapping;
  50. import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
  51. import org.datanucleus.store.scostore.ListStore;
  52. import org.datanucleus.store.scostore.SetStore;
  53. import org.datanucleus.util.ClassUtils;
  54. import org.datanucleus.util.Localiser;
  55. import org.datanucleus.util.NucleusLogger;
  56. import java.util.Arrays;
  57. import java.util.Collection;
  58. import java.util.Collections;
  59. import java.util.HashSet;
  60. import java.util.Map;
  61. import java.util.Set;
  62. import java.util.concurrent.ConcurrentHashMap;
  63. /**
  64. * StoreManager for GAE/J with DataNucleus.
  65. * When the user specifies "datanucleus.ConnectionURL" as "appengine", an instance of this class is created.
  66. * <h3>Information stored for each object</h3>
  67. * <p>
  68. * When an object of a class is stored, we can store the following information as properties of the Entity.
  69. * <ul>
  70. * <li>Primary key (which includes parentKey where required)</li>
  71. * <li>Discriminator (if defined)</li>
  72. * <li>Version (if defined)</li>
  73. * <li>Each non-null field
  74. * <ul>
  75. * <li>Unowned relation fields will store the Key(s) of related objects (dependent on StorageVersion)</li>
  76. * <li>Owned relation fields at parent side will store the Key(s) of child objects (dependent on StorageVersion)</li>
  77. * <li>Owned relation fields at child side will not store anything about the parent</li>
  78. * <li>Non-relation fields store the value (as long as supported type).</li>
  79. * </ul></li>
  80. * <li>Field(s) of embedded object.</li>
  81. * </ul>
  82. * </p>
  83. * <h3>Transaction Restrictions</h3>
  84. * <p>
  85. * The GAE datastore has some restrictions to be aware of when inspecting the code in this plugin. These
  86. * restrictions impact on the design of the plugin.
  87. * <ul>
  88. * <li>When we PUT an object it must have its parent assigned then (create of Entity), and not later. In particular
  89. * see the handling of owned relations in DatastorePersistenceHandler, and the order of PUTs, making sure we have
  90. * the parent first before any child is PUT.</li>
  91. * <li>All objects have to be in the same entity-group, except when using XG transactions.</li>
  92. * </ul>
  93. * </p>
  94. *
  95. * @author Max Ross <maxr@google.com>
  96. */
  97. public class DatastoreManager extends MappedStoreManager {
  98. protected static final Localiser GAE_LOCALISER = Localiser.getInstance(
  99. "com.google.appengine.datanucleus.Localisation", DatastoreManager.class.getClassLoader());
  100. private static final String EXTENSION_PREFIX = "gae.";
  101. /** The name of the metadata extension that marks a field as a parent. */
  102. public static final String PARENT_PK = EXTENSION_PREFIX + "parent-pk";
  103. /** The name of the metadata extension that marks a field as an encoded pk. */
  104. public static final String ENCODED_PK = EXTENSION_PREFIX + "encoded-pk";
  105. /** The name of the metadata extension that marks a field as a primary key name. */
  106. public static final String PK_NAME = EXTENSION_PREFIX + "pk-name";
  107. /** The name of the metadata extension that marks a field as a primary key id. */
  108. public static final String PK_ID = EXTENSION_PREFIX + "pk-id";
  109. /** The name of the metadata extension that marks a field as unindexed. */
  110. public static final String UNINDEXED_PROPERTY = EXTENSION_PREFIX + "unindexed";
  111. public static final String DATASTORE_READ_CONSISTENCY_PROPERTY =
  112. "datanucleus.appengine.datastoreReadConsistency";
  113. public static final String DATASTORE_ENABLE_XG_TXNS_PROPERTY =
  114. "datanucleus.appengine.datastoreEnableXGTransactions";
  115. public static final String GET_EXTENT_CAN_RETURN_SUBCLASSES_PROPERTY =
  116. "datanucleus.appengine.getExtentCanReturnSubclasses";
  117. /** Property to set the default type of relations ("owned", "unowned") with default as "owned". */
  118. public static final String RELATION_DEFAULT_MODE = "datanucleus.appengine.relationDefault";
  119. /** Property allowing the user to turn off GAE/J-specific validation of metadata and assume its ok. */
  120. public static final String VALIDATE_METADATA = "datanucleus.appengine.validateMetaData";
  121. /**
  122. * A property that is expected to be set to either "String" or "Double". The
  123. * default is Double.
  124. *
  125. * Double indicates that BigDecimals should be stored as a double, and
  126. * therefore lose precision when stored.
  127. *
  128. * String indicates they should be serialized to a string using the
  129. * {@link BigDecimals} class. This will result in retaining their precision
  130. * when decoded. It is still possible to use inequality queries against them.
  131. * For this reason string is generally preferable.
  132. *
  133. *
  134. * Inequality queries for fields where some values are doubles and some are
  135. * encoded as strings will not work.
  136. */
  137. public static final String BIG_DECIMALS_ENCODEING = "datanucleus.appengine.BigDecimalsEncoding";
  138. /**
  139. * The name of the extension that indicates the return value of the batch
  140. * delete query should be as accurate as possible at the expense of
  141. * fulfilling the query less efficiently. If this extension is set and
  142. * {@code true}, the query will be fulfilled by first fetching the entities
  143. * and then deleting those that are returned. This involves an additional
  144. * roundtrip to the datastore but allows us to return a more accurate count
  145. * of the number of records that were deleted (we say more accurate as
  146. * opposed to accurate because it's possible that entity was deleted in
  147. * between the fetch and the delete and we wouldn't have any way of knowing).
  148. * If this extension is not set or is set with a value of {@code false},
  149. * we'll just execute a batch delete directly and use the number of entities
  150. * we were asked to delete as the return value.
  151. */
  152. public static final String QUERYEXT_SLOW_BUT_MORE_ACCURATE_JPQL_DELETE =
  153. EXTENSION_PREFIX + "slow-but-more-accurate-jpql-delete-query";
  154. /** Query extension that indicates the query should be excluded from the current transaction. */
  155. public static final String QUERYEXT_EXCLUDE_FROM_TXN = EXTENSION_PREFIX + "exclude-query-from-txn";
  156. /** Query extension to enable/disable use of in-memory evaluation when some syntax is unsupported in datastore. */
  157. public static final String QUERYEXT_INMEMORY_WHEN_UNSUPPORTED = EXTENSION_PREFIX + "inmemory-when-unsupported";
  158. /**
  159. * Classes whose metadata we've validated. This set gets hit on every
  160. * insert, update, and fetch. I don't expect it to be a bottleneck but
  161. * if we're seeing contention we should look here.
  162. */
  163. private final Set<String> validatedClasses = Collections.synchronizedSet(new HashSet<String>());
  164. /** Map of the metadata for the member of this class storing the parent ("gae.parent-pk") keyed by class name. */
  165. private final Map<String, AbstractMemberMetaData> parentMemberMetaDataByClass =
  166. new ConcurrentHashMap<String, AbstractMemberMetaData>();
  167. private final boolean defaultToOwnedRelations;
  168. private final TypeConversionUtils typeConversionUtils;
  169. private final StorageVersion storageVersion;
  170. private final DatastoreServiceConfig defaultDatastoreServiceConfigPrototypeForReads;
  171. private final DatastoreServiceConfig defaultDatastoreServiceConfigPrototypeForWrites;
  172. private final TransactionOptions defaultDatastoreTransactionOptionsPrototype;
  173. private final DatastoreService datastoreServiceForReads;
  174. protected SerializationManager serializationMgr = null;
  175. MetaDataValidator metadataValidator;
  176. /**
  177. * Construct a DatastoreManager.
  178. * @param clr The ClassLoaderResolver
  179. * @param nucContext The NucleusContext
  180. * @param props Properties to store on this StoreManager
  181. */
  182. public DatastoreManager(ClassLoaderResolver clr, NucleusContext nucContext, Map<String, Object> props)
  183. throws NoSuchFieldException, IllegalAccessException {
  184. super("appengine", clr, nucContext, props);
  185. // Override some of the default property values for AppEngine
  186. PersistenceConfiguration conf = nucContext.getPersistenceConfiguration();
  187. conf.setProperty("datanucleus.attachSameDatastore", Boolean.TRUE.toString()); // Always only one datastore
  188. // We'd like to respect the user's selection here, but the default value is 1.
  189. // This is problematic for us in the situation where, for example, an embedded object
  190. // gets updated more than once in a txn because we end up putting the same entity twice.
  191. // TODO(maxr) Remove this once we support multiple puts
  192. conf.setProperty("datanucleus.datastoreTransactionFlushLimit", Integer.MAX_VALUE);
  193. // Install our key translator
  194. conf.setProperty("datanucleus.identityKeyTranslatorType", "appengine");
  195. // Check if datastore api is in CLASSPATH. Don't let the hard-coded
  196. // jar name upset you, it's just used for error messages. The check will
  197. // succeed so long as the class is available on the classpath
  198. ClassUtils.assertClassForJarExistsInClasspath(
  199. clr, "com.google.appengine.api.datastore.DatastoreService", "appengine-api.jar");
  200. defaultDatastoreServiceConfigPrototypeForReads =
  201. createDatastoreServiceConfigPrototypeForReads(nucContext.getPersistenceConfiguration());
  202. defaultDatastoreServiceConfigPrototypeForWrites =
  203. createDatastoreServiceConfigPrototypeForWrites(nucContext.getPersistenceConfiguration());
  204. defaultDatastoreTransactionOptionsPrototype =
  205. createDatastoreTransactionOptionsPrototype(nucContext.getPersistenceConfiguration());
  206. // Handler for persistence process
  207. persistenceHandler = new DatastorePersistenceHandler(this);
  208. flushProcess = new FlushOrdered();
  209. dba = new DatastoreAdapter();
  210. initialiseIdentifierFactory(nucContext);
  211. storageVersion = StorageVersion.fromStoreManager(this);
  212. String defaultRelationMode = getStringProperty(RELATION_DEFAULT_MODE);
  213. defaultToOwnedRelations = defaultRelationMode.equalsIgnoreCase("unowned") ? false : true;
  214. String bigDecimalsEncoding = getStringProperty(BIG_DECIMALS_ENCODEING);
  215. typeConversionUtils =
  216. "String".equalsIgnoreCase(bigDecimalsEncoding) ? new TypeConversionUtils(true)
  217. : new TypeConversionUtils(false);
  218. // Add listener so we can check all metadata for unsupported features and required schema
  219. metadataValidator = new MetaDataValidator(this, getMetaDataManager(), clr);
  220. logConfiguration();
  221. datastoreServiceForReads = DatastoreServiceFactoryInternal.getDatastoreService(
  222. getDefaultDatastoreServiceConfigForReads());
  223. }
  224. @Override
  225. public void close() {
  226. validatedClasses.clear();
  227. super.close();
  228. }
  229. public SerializationManager getSerializationManager() {
  230. if (serializationMgr == null) {
  231. serializationMgr = new SerializationManager();
  232. }
  233. return serializationMgr;
  234. }
  235. public boolean isDefaultToOwnedRelations() {
  236. return defaultToOwnedRelations;
  237. }
  238. /**
  239. * Convenience method to log the configuration of this store manager.
  240. */
  241. protected void logConfiguration()
  242. {
  243. super.logConfiguration();
  244. if (NucleusLogger.DATASTORE.isDebugEnabled())
  245. {
  246. NucleusLogger.DATASTORE.debug("StorageVersion : " + storageVersion.toString());
  247. NucleusLogger.DATASTORE.debug("Default Relation Mode : " + getStringProperty(RELATION_DEFAULT_MODE));
  248. NucleusLogger.DATASTORE.debug("===========================================================");
  249. }
  250. }
  251. @Override
  252. public void transactionStarted(ExecutionContext ec) {
  253. if (connectionFactoryIsAutoCreateTransaction()) {
  254. // Obtain the ManagedConnection, triggering XAResource.start, so calling DatastoreService.beginTransaction()
  255. getConnection(ec);
  256. }
  257. }
  258. @Override
  259. public NucleusConnection getNucleusConnection(ExecutionContext ec) {
  260. ConnectionFactory cf = connectionMgr.lookupConnectionFactory(primaryConnectionFactoryName);
  261. final ManagedConnection mc;
  262. final boolean enlisted;
  263. enlisted = ec.getTransaction().isActive();
  264. mc = cf.getConnection(enlisted ? ec : null, ec.getTransaction(), null); // Will throw exception if already locked
  265. // Lock the connection now that it is in use by the user
  266. mc.lock();
  267. Runnable closeRunnable = new Runnable() {
  268. public void run() {
  269. // Unlock the connection now that the user has finished with it
  270. mc.unlock();
  271. if (!enlisted) {
  272. // TODO Anything to do here?
  273. }
  274. }
  275. };
  276. return new NucleusConnectionImpl(mc.getConnection(), closeRunnable);
  277. }
  278. // TODO Remove this when we support subclasses from a query
  279. @Override
  280. public Extent getExtent(ExecutionContext ec, Class c, boolean subclasses) {
  281. AbstractClassMetaData cmd = getMetaDataManager().getMetaDataForClass(c, ec.getClassLoaderResolver());
  282. validateMetaDataForClass(cmd);
  283. if (!cmd.isRequiresExtent()) {
  284. throw new NoExtentException(c.getName());
  285. }
  286. if (!getBooleanProperty(GET_EXTENT_CAN_RETURN_SUBCLASSES_PROPERTY, false)) {
  287. subclasses = false;
  288. }
  289. // In order to avoid breaking existing apps I'm hard-coding subclasses to
  290. // be false. This breaks spec compliance since the no-arg overload of
  291. // PersistenceManager.getExtent() is supposed to return subclasses.
  292. return new DefaultCandidateExtent(ec, c, subclasses, cmd);
  293. }
  294. /**
  295. * Method to return the default identifier case.
  296. * @return Identifier case to use if not specified by the user
  297. */
  298. public String getDefaultIdentifierCase() {
  299. return "PreserveCase";
  300. }
  301. @Override
  302. public Collection<String> getSupportedOptions() {
  303. Set<String> opts = new HashSet<String>();
  304. opts.add("TransactionIsolationLevel.read-committed");
  305. opts.add("BackedSCO"); // TODO Only use this when using original StorageVersion
  306. opts.add("ApplicationIdentity");
  307. opts.add("DatastoreIdentity");
  308. opts.add("OptimisticTransaction");
  309. opts.add("ORM");
  310. return opts;
  311. }
  312. @Override
  313. public DatastoreContainerObject newJoinDatastoreContainerObject(AbstractMemberMetaData fmd,
  314. ClassLoaderResolver clr) {
  315. return null;
  316. }
  317. /**
  318. * This method requires synchronization so that we don't end up registering
  319. * the same property more than once.
  320. */
  321. @Override
  322. protected synchronized StoreData newStoreData(ClassMetaData cmd, ClassLoaderResolver clr) {
  323. InheritanceStrategy strat = cmd.getInheritanceMetaData().getStrategy();
  324. // The overarching rule for supported inheritance strategies is that we
  325. // don't split the state of an object across multiple entities.
  326. // TODO This is all nonsense. This datastore only allows "COMPLETE_TABLE" really so ignore the inheritance
  327. // and remove the need for DatastoreClass
  328. if (strat == InheritanceStrategy.SUBCLASS_TABLE) {
  329. // Table mapped into the table(s) of subclass(es)
  330. // Just add the SchemaData entry with no table - managed by subclass
  331. return buildStoreDataWithNoTable(cmd);
  332. } else if (strat == InheritanceStrategy.COMPLETE_TABLE) {
  333. if (cmd.isAbstract()) {
  334. // Abstract class with "complete-table" so gets no table
  335. return buildStoreDataWithNoTable(cmd);
  336. }
  337. return buildStoreData(cmd, clr);
  338. } else if (strat == InheritanceStrategy.NEW_TABLE &&
  339. (cmd.getSuperAbstractClassMetaData() == null ||
  340. cmd.getSuperAbstractClassMetaData().getInheritanceMetaData().getStrategy() == InheritanceStrategy.SUBCLASS_TABLE)) {
  341. // New Table means you store your fields and your fields only (no fields from superclasses).
  342. // This only ok if you don't have a persistent superclass or your persistent superclass has
  343. // delegated responsibility for storing its fields to you.
  344. return buildStoreData(cmd, clr);
  345. } else if (MetaDataUtils.isNewOrSuperclassTableInheritanceStrategy(cmd)) {
  346. // Table mapped into table of superclass
  347. // Find the superclass - should have been created first
  348. AbstractClassMetaData[] managingCmds = getClassesManagingTableForClass(cmd, clr);
  349. DatastoreTable superTable;
  350. if (managingCmds != null && managingCmds.length == 1) {
  351. MappedStoreData superData = (MappedStoreData) storeDataMgr.get(managingCmds[0].getFullClassName());
  352. if (superData != null) {
  353. // Specify the table if it already exists
  354. superTable = (DatastoreTable) superData.getDatastoreContainerObject();
  355. return buildStoreDataWithTable(cmd, superTable);
  356. }
  357. }
  358. }
  359. // TODO Don't do this. GAE basically supports "complete-table" always so we should just ignore any
  360. // inheritance metadata that implies otherwise.
  361. boolean jpa = getApiAdapter().getName().equalsIgnoreCase("JPA");
  362. String unsupportedMsg = GAE_LOCALISER.msg(jpa ? "AppEngine.BadInheritance.JPA" : "AppEngine.BadInheritance.JDO",
  363. cmd.getInheritanceMetaData().getStrategy().toString(), cmd.getFullClassName(), getApiAdapter().getName());
  364. throw new UnsupportedInheritanceStrategyException(unsupportedMsg);
  365. }
  366. private StoreData buildStoreDataWithNoTable(ClassMetaData cmd) {
  367. StoreData sdNew = new MappedStoreData(cmd, null, false);
  368. registerStoreData(sdNew);
  369. return sdNew;
  370. }
  371. private StoreData buildStoreData(ClassMetaData cmd, ClassLoaderResolver clr) {
  372. String kindName = EntityUtils.getKindName(getIdentifierFactory(), cmd);
  373. DatastoreTable table = new DatastoreTable(kindName, this, cmd, clr, dba);
  374. StoreData sd = new MappedStoreData(cmd, table, true);
  375. registerStoreData(sd);
  376. // needs to be called after we register the store data to avoid stack overflow
  377. table.buildMapping();
  378. return sd;
  379. }
  380. private StoreData buildStoreDataWithTable(ClassMetaData cmd, DatastoreTable table) {
  381. MappedStoreData sd = new MappedStoreData(cmd, table, false);
  382. registerStoreData(sd);
  383. sd.setDatastoreContainerObject(table);
  384. table.manageClass(cmd);
  385. return sd;
  386. }
  387. @Override
  388. public Object getResultValueAtPosition(Object key, JavaTypeMapping mapping, int position) {
  389. // this is the key, and we're only using this for keys, so just return it.
  390. return key;
  391. }
  392. @Override
  393. public boolean allowsBatching() {
  394. return false;
  395. }
  396. public FieldManager getFieldManagerForResultProcessing(ObjectProvider op, Object resultSet,
  397. StatementClassMapping resultMappings) {
  398. ExecutionContext ec = op.getExecutionContext();
  399. Class<?> cls = ec.getClassLoaderResolver().classForName(op.getClassMetaData().getFullClassName());
  400. Object internalKey = EntityUtils.idToInternalKey(ec, cls, resultSet, true);
  401. // Need to provide this to the field manager in the form of the pk
  402. // of the type: Key, Long, encoded String, or unencoded String
  403. return new KeyOnlyFieldManager(internalKey);
  404. }
  405. @Override
  406. public FieldManager getFieldManagerForResultProcessing(ExecutionContext ec, Object resultSet,
  407. StatementClassMapping resultMappings, AbstractClassMetaData cmd) {
  408. Class<?> cls = ec.getClassLoaderResolver().classForName(cmd.getFullClassName());
  409. Object internalKey = EntityUtils.idToInternalKey(ec, cls, resultSet, true);
  410. // Need to provide this to the field manager in the form of the pk
  411. // of the type: Key, Long, encoded String, or unencoded String
  412. return new KeyOnlyFieldManager(internalKey);
  413. }
  414. protected ListStore newFKListStore(AbstractMemberMetaData ammd, ClassLoaderResolver clr) {
  415. // TODO Delete this when we drop old storage version
  416. return new FKListStore(ammd, this, clr);
  417. }
  418. @Override
  419. protected SetStore newFKSetStore(AbstractMemberMetaData ammd, ClassLoaderResolver clr) {
  420. // TODO Delete this when we drop old storage version
  421. return new FKSetStore(ammd, this, clr);
  422. }
  423. public boolean usesBackedSCOWrappers() {
  424. // TODO Use simple SCO wrappers at some point in future since we have no need for the backed variants now
  425. /*if (storageVersionAtLeast(StorageVersion.READ_OWNED_CHILD_KEYS_FROM_PARENTS)) {
  426. return false;
  427. }*/
  428. return true;
  429. }
  430. public boolean useBackedSCOWrapperForMember(AbstractMemberMetaData mmd, ExecutionContext ec) {
  431. // Use backed SCO wrapper on relation field (to support legacy), and use simple SCO wrapper on others
  432. return (mmd.getRelationType(ec.getClassLoaderResolver()) == RelationType.NONE ? false : true);
  433. }
  434. public boolean storageVersionAtLeast(StorageVersion storageVersion) {
  435. return getStorageVersion().ordinal() >= storageVersion.ordinal();
  436. }
  437. public TransactionOptions getDefaultDatastoreTransactionOptions() {
  438. return copyTransactionOptions(defaultDatastoreTransactionOptionsPrototype);
  439. }
  440. @Override
  441. public DatastorePersistenceHandler getPersistenceHandler() {
  442. return (DatastorePersistenceHandler) super.getPersistenceHandler();
  443. }
  444. @Override
  445. public DatastoreTable getDatastoreClass(String className, ClassLoaderResolver clr) {
  446. try {
  447. // We see the occasional race condition when multiple threads concurrently
  448. // perform an operation using a persistence-capable class for which DataNucleus
  449. // has not yet generated the meta-data. The result is usually
  450. // AbstractMemberMetaData objects with the same column listed twice in the meta-data.
  451. // Locking at this level is a bit more coarse-grained than I'd like but once the
  452. // meta-data has been built this will return super fast so it shouldn't be an issue.
  453. synchronized(this) {
  454. return (DatastoreTable) super.getDatastoreClass(className, clr);
  455. }
  456. } catch (NoTableManagedException e) {
  457. // Our parent class throws this when the class isn't PersistenceCapable also.
  458. Class cls = clr.classForName(className);
  459. ApiAdapter api = getApiAdapter();
  460. if (cls != null && !cls.isInterface() && !api.isPersistable(cls)) {
  461. throw new NoTableManagedException(
  462. "Class " + className + " does not seem to have been enhanced. You may want to rerun " +
  463. "the enhancer and check for errors in the output.");
  464. // Suggest you address why this method is being called before any check on whether it is persistable
  465. // then you can remove this error message
  466. }
  467. throw e;
  468. }
  469. }
  470. /**
  471. * Perform appengine-specific validation on the provided meta data.
  472. * Also generates cached information that is needed by persistence.
  473. * @param cmd The metadata to validate.
  474. */
  475. public void validateMetaDataForClass(AbstractClassMetaData cmd) {
  476. // Only validate each meta data once
  477. if (validatedClasses.add(cmd.getFullClassName())) {
  478. if (getBooleanProperty(VALIDATE_METADATA, true)) {
  479. // Only do if the persistence property is not set to false
  480. metadataValidator.validate(cmd);
  481. }
  482. AbstractMemberMetaData parentPkMmd = MetaDataUtils.getParentPkMemberMetaDataForClass(cmd, getMetaDataManager(),
  483. getNucleusContext().getClassLoaderResolver(cmd.getClass().getClassLoader()));
  484. if (parentPkMmd != null) {
  485. parentMemberMetaDataByClass.put(cmd.getFullClassName(), parentPkMmd);
  486. }
  487. }
  488. }
  489. /**
  490. * Accessor for the metadata of the member of this class marked as "gae.parent-pk".
  491. * @param cmd Metadata for the class
  492. * @return The member marked as "gae.parent-pk".
  493. */
  494. public AbstractMemberMetaData getMetaDataForParentPK(AbstractClassMetaData cmd) {
  495. return parentMemberMetaDataByClass.get(cmd.getFullClassName());
  496. }
  497. public StorageVersion getStorageVersion() {
  498. return storageVersion;
  499. }
  500. public static final class UnsupportedInheritanceStrategyException extends NucleusFatalUserException {
  501. UnsupportedInheritanceStrategyException(String msg) {
  502. super(msg);
  503. }
  504. }
  505. /**
  506. * Accessor for the current DatastoreTransaction for this ExecutionContext.
  507. * Each PM/EM has its own DatastoreService, and consequently can have a current DatastoreTransaction.
  508. * @param ec ExecutionContext
  509. * @return The DatastoreTransaction if active, or null
  510. */
  511. public DatastoreTransaction getDatastoreTransaction(ExecutionContext ec) {
  512. ManagedConnection mconn = ec.getStoreManager().getConnection(ec);
  513. return ((EmulatedXAResource) mconn.getXAResource()).getCurrentTransaction();
  514. }
  515. /**
  516. * Accessor for the current DatastoreService for this ExecutionContext.
  517. * Each PM/EM has its own DatastoreService.
  518. * @param ec ExecutionContext
  519. * @return The DatastoreService
  520. */
  521. public DatastoreService getDatastoreService(ExecutionContext ec) {
  522. ManagedConnection mconn = ec.getStoreManager().getConnection(ec);
  523. return ((EmulatedXAResource) mconn.getXAResource()).getDatastoreService();
  524. }
  525. /**
  526. * Accessor for the DatastoreService to use for reads for this ExecutionContext.
  527. * We currently just use the same DatastoreService for all ExecutionContext, but parameter passed in so
  528. * we can extend in the future.
  529. * @param ec ExecutionContext
  530. * @return The DatastoreService
  531. */
  532. public DatastoreService getDatastoreServiceForReads(ExecutionContext ec) {
  533. return datastoreServiceForReads;
  534. }
  535. /**
  536. * Helper method to determine if the connection factory associated with this
  537. * manager is AutoCreateTransaction.
  538. */
  539. public boolean connectionFactoryIsAutoCreateTransaction() {
  540. DatastoreConnectionFactoryImpl connFactory =
  541. (DatastoreConnectionFactoryImpl) connectionMgr.lookupConnectionFactory(primaryConnectionFactoryName);
  542. return connFactory.isAutoCreateTransaction();
  543. }
  544. /**
  545. * Returns a fresh instance so that callers can make changes without
  546. * impacting global state.
  547. */
  548. public DatastoreServiceConfig getDefaultDatastoreServiceConfigForReads() {
  549. return copyDatastoreServiceConfig(defaultDatastoreServiceConfigPrototypeForReads);
  550. }
  551. /**
  552. * Returns a fresh instance so that callers can make changes without
  553. * impacting global state.
  554. */
  555. public DatastoreServiceConfig getDefaultDatastoreServiceConfigForWrites() {
  556. return copyDatastoreServiceConfig(defaultDatastoreServiceConfigPrototypeForWrites);
  557. }
  558. private DatastoreServiceConfig createDatastoreServiceConfigPrototypeForReads(
  559. PersistenceConfiguration persistenceConfig) {
  560. return createDatastoreServiceConfigPrototype(persistenceConfig, PropertyNames.PROPERTY_DATASTORE_READ_TIMEOUT);
  561. }
  562. private DatastoreServiceConfig createDatastoreServiceConfigPrototypeForWrites(
  563. PersistenceConfiguration persistenceConfig) {
  564. return createDatastoreServiceConfigPrototype(persistenceConfig, PropertyNames.PROPERTY_DATASTORE_WRITE_TIMEOUT);
  565. }
  566. private DatastoreServiceConfig createDatastoreServiceConfigPrototype(
  567. PersistenceConfiguration persistenceConfiguration, String... timeoutProps) {
  568. DatastoreServiceConfig datastoreServiceConfig = DatastoreServiceConfig.Builder.withDefaults();
  569. for (String timeoutProp : timeoutProps) {
  570. int defaultDeadline = persistenceConfiguration.getIntProperty(timeoutProp);
  571. if (defaultDeadline > 0) {
  572. datastoreServiceConfig.deadline(defaultDeadline / 1000d);
  573. }
  574. }
  575. String defaultReadConsistencyStr = persistenceConfiguration.getStringProperty(
  576. DATASTORE_READ_CONSISTENCY_PROPERTY);
  577. if (defaultReadConsistencyStr != null) {
  578. try {
  579. datastoreServiceConfig.readPolicy(new ReadPolicy(Consistency.valueOf(defaultReadConsistencyStr)));
  580. } catch (IllegalArgumentException iae) {
  581. throw new NucleusFatalUserException(
  582. "Illegal value for " + DATASTORE_READ_CONSISTENCY_PROPERTY +
  583. ". Valid values are " + Arrays.toString(Consistency.values()));
  584. }
  585. }
  586. return datastoreServiceConfig;
  587. }
  588. @SuppressWarnings("deprecation")
  589. private TransactionOptions createDatastoreTransactionOptionsPrototype(
  590. PersistenceConfiguration persistenceConfig) {
  591. // return TransactionOptions.Builder.withXG(
  592. return TransactionOptions.Builder.allowMultipleEntityGroups(
  593. persistenceConfig.getBooleanProperty(DATASTORE_ENABLE_XG_TXNS_PROPERTY));
  594. }
  595. public TypeConversionUtils getTypeConversionUtils() {
  596. return typeConversionUtils;
  597. }
  598. // visible for testing
  599. static DatastoreServiceConfig copyDatastoreServiceConfig(DatastoreServiceConfig config) {
  600. DatastoreServiceConfig newConfig = DatastoreServiceConfig.Builder.
  601. withImplicitTransactionManagementPolicy(config.getImplicitTransactionManagementPolicy()).
  602. readPolicy(config.getReadPolicy());
  603. if (config.getDeadline() != null) {
  604. newConfig.deadline(config.getDeadline());
  605. }
  606. return newConfig;
  607. }
  608. // visible for testing
  609. static TransactionOptions copyTransactionOptions(TransactionOptions txnOpts) {
  610. // Maintenance nightmare, use clone() once it's available in the sdk
  611. TransactionOptions options = TransactionOptions.Builder.withDefaults();
  612. if (txnOpts.isXG()) {
  613. options.setXG(txnOpts.isXG());
  614. } else {
  615. options.clearXG();
  616. }
  617. return txnOpts;
  618. }
  619. }