PageRenderTime 53ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/src/NHibernate.Shards/src/NHibernate.Shards/Session/ShardedSessionImpl.cs

https://bitbucket.org/dabide/nhcontrib
C# | 2509 lines | 1553 code | 238 blank | 718 comment | 176 complexity | 8a4f8a8f66a263a88ade31517d953a4a MD5 | raw file
Possible License(s): BSD-3-Clause, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0, Apache-2.0, LGPL-3.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Collections.ObjectModel;
  5. using System.Data;
  6. using System.Linq.Expressions;
  7. using System.Runtime.Serialization;
  8. using Iesi.Collections.Generic;
  9. using log4net;
  10. using System.Linq;
  11. using NHibernate.Engine;
  12. using NHibernate.Id;
  13. using NHibernate.Metadata;
  14. using NHibernate.Proxy;
  15. using NHibernate.Shards.Criteria;
  16. using NHibernate.Shards.Engine;
  17. using NHibernate.Shards.Id;
  18. using NHibernate.Shards.Query;
  19. using NHibernate.Shards.Stat;
  20. using NHibernate.Shards.Strategy;
  21. using NHibernate.Shards.Strategy.Exit;
  22. using NHibernate.Shards.Strategy.Selection;
  23. using NHibernate.Shards.Transaction;
  24. using NHibernate.Shards.Util;
  25. using NHibernate.Stat;
  26. using NHibernate.Type;
  27. namespace NHibernate.Shards.Session
  28. {
  29. /// <summary>
  30. /// Concrete implementation of a ShardedSession, and also the central component of
  31. /// Hibernate Shards' internal implementation. This class exposes two interfaces;
  32. /// ShardedSession itself, to the application, and ShardedSessionImplementor, to
  33. /// other components of Hibernate Shards. This class is not threadsafe.
  34. /// </summary>
  35. public class ShardedSessionImpl : IShardedSession, IShardedSessionImplementor, IShardIdResolver
  36. {
  37. [ThreadStatic]
  38. private static ShardId currentSubgraphShardId;
  39. private readonly bool checkAllAssociatedObjectsForDifferentShards;
  40. private readonly Set<System.Type> classesWithoutTopLevelSaveSupport;
  41. private readonly ILog log = LogManager.GetLogger(typeof(ShardedSessionImpl));
  42. private readonly IShardedSessionFactoryImplementor shardedSessionFactory;
  43. private readonly IDictionary<ShardId, IShard> shardIdsToShards;
  44. private readonly List<IShard> shards;
  45. private readonly IShardStrategy shardStrategy;
  46. private bool closed = false;
  47. private bool lockedShard = false;
  48. private ShardId lockedShardId;
  49. // access to sharded session is single-threaded so we can use a non-atomic
  50. // counter for criteria ids or query ids
  51. private int nextCriteriaId = 0;
  52. private int nextQueryId = 0;
  53. private IShardedTransaction transaction;
  54. /**
  55. * Constructor used for openSession(...) processing.
  56. *
  57. * @param shardedSessionFactory The factory from which this session was obtained
  58. * @param shardStrategy The shard strategy for this session
  59. * @param classesWithoutTopLevelSaveSupport The set of classes on which top-level save can not be performed
  60. * @param checkAllAssociatedObjectsForDifferentShards Should we check for cross-shard relationships
  61. */
  62. internal ShardedSessionImpl(
  63. IShardedSessionFactoryImplementor shardedSessionFactory,
  64. IShardStrategy shardStrategy,
  65. Set<System.Type> classesWithoutTopLevelSaveSupport,
  66. bool checkAllAssociatedObjectsForDifferentShards)
  67. : this(null,
  68. shardedSessionFactory,
  69. shardStrategy,
  70. classesWithoutTopLevelSaveSupport,
  71. checkAllAssociatedObjectsForDifferentShards)
  72. {
  73. }
  74. /**
  75. * Constructor used for openSession(...) processing.
  76. *
  77. * @param interceptor The interceptor to be applied to this session
  78. * @param shardedSessionFactory The factory from which this session was obtained
  79. * @param shardStrategy The shard strategy for this session
  80. * @param classesWithoutTopLevelSaveSupport The set of classes on which top-level save can not be performed
  81. * @param checkAllAssociatedObjectsForDifferentShards Should we check for cross-shard relationships
  82. */
  83. internal ShardedSessionImpl(
  84. /*@Nullable*/ IInterceptor interceptor,
  85. IShardedSessionFactoryImplementor shardedSessionFactory,
  86. IShardStrategy shardStrategy,
  87. Set<System.Type> classesWithoutTopLevelSaveSupport,
  88. bool checkAllAssociatedObjectsForDifferentShards)
  89. {
  90. this.shardedSessionFactory = shardedSessionFactory;
  91. shards =
  92. BuildShardListFromSessionFactoryShardIdMap(shardedSessionFactory.GetSessionFactoryShardIdMap(),
  93. checkAllAssociatedObjectsForDifferentShards, this, interceptor);
  94. shardIdsToShards = BuildShardIdsToShardsMap();
  95. this.shardStrategy = shardStrategy;
  96. this.classesWithoutTopLevelSaveSupport = classesWithoutTopLevelSaveSupport;
  97. this.checkAllAssociatedObjectsForDifferentShards = checkAllAssociatedObjectsForDifferentShards;
  98. }
  99. public static ShardId CurrentSubgraphShardId
  100. {
  101. get { return currentSubgraphShardId; }
  102. set { currentSubgraphShardId = value; }
  103. }
  104. public ISession SomeSession
  105. {
  106. get
  107. {
  108. foreach (IShard shard in shards)
  109. {
  110. if (shard.Session != null)
  111. {
  112. return shard.Session;
  113. }
  114. }
  115. return null;
  116. }
  117. }
  118. #region IShardedSession Members
  119. /// <summary>
  120. /// Gets the non-sharded session with which the objects is associated.
  121. /// </summary>
  122. /// <param name="obj">the object for which we want the Session</param>
  123. /// <returns>
  124. /// The Session with which this object is associated, or null if the
  125. /// object is not associated with a session belonging to this ShardedSession
  126. /// </returns>
  127. public ISession GetSessionForObject(object obj)
  128. {
  129. return GetSessionForObject(obj, shards);
  130. }
  131. /// <summary>
  132. /// Gets the ShardId of the shard with which the objects is associated.
  133. /// </summary>
  134. /// <param name="obj">the object for which we want the Session</param>
  135. /// <returns>
  136. /// the ShardId of the Shard with which this object is associated, or
  137. /// null if the object is not associated with a shard belonging to this
  138. /// ShardedSession
  139. /// </returns>
  140. public ShardId GetShardIdForObject(object obj)
  141. {
  142. return GetShardIdForObject(obj, shards);
  143. }
  144. /// <summary>
  145. /// Place the session into a state where every create operation takes place
  146. /// on the same shard. Once the shard is locked on a session it cannot
  147. /// be unlocked.
  148. /// </summary>
  149. public void LockShard()
  150. {
  151. lockedShard = true;
  152. }
  153. /// <summary>
  154. /// Force the <c>ISession</c> to flush.
  155. /// </summary>
  156. /// <remarks>
  157. /// Must be called at the end of a unit of work, before commiting the transaction and closing
  158. /// the session (<c>Transaction.Commit()</c> calls this method). <i>Flushing</i> if the process
  159. /// of synchronising the underlying persistent store with persistable state held in memory.
  160. /// </remarks>
  161. public void Flush()
  162. {
  163. foreach (IShard shard in shards)
  164. {
  165. // unopened sessions won't have anything to flush
  166. if (shard.Session != null)
  167. {
  168. shard.Session.Flush();
  169. }
  170. }
  171. }
  172. /// <summary>
  173. /// Determines at which points Hibernate automatically flushes the session.
  174. /// </summary>
  175. /// <remarks>
  176. /// For a readonly session, it is reasonable to set the flush mode to <c>FlushMode.Never</c>
  177. /// at the start of the session (in order to achieve some extra performance).
  178. /// </remarks>
  179. public FlushMode FlushMode
  180. {
  181. get
  182. {
  183. // all shards must have the same flush mode
  184. ISession someSession = SomeSession;
  185. if (someSession == null)
  186. {
  187. someSession = shards[0].EstablishSession();
  188. }
  189. return someSession.FlushMode;
  190. }
  191. set
  192. {
  193. var @event = new SetFlushModeOpenSessionEvent(value);
  194. foreach (IShard shard in shards)
  195. {
  196. if (shard.Session != null)
  197. {
  198. shard.Session.FlushMode = value;
  199. }
  200. else
  201. {
  202. shard.AddOpenSessionEvent(@event);
  203. }
  204. }
  205. }
  206. }
  207. /// <summary>
  208. /// The current cache mode.
  209. /// </summary>
  210. /// <remarks>
  211. /// Cache mode determines the manner in which this session can interact with
  212. /// the second level cache.
  213. /// </remarks>
  214. public CacheMode CacheMode
  215. {
  216. get
  217. {
  218. // all shards must have the same cache mode
  219. ISession someSession = SomeSession;
  220. if (someSession == null)
  221. {
  222. someSession = shards[0].EstablishSession();
  223. }
  224. return someSession.CacheMode;
  225. }
  226. set
  227. {
  228. var @event = new SetCacheModeOpenSessionEvent(value);
  229. foreach (IShard shard in shards)
  230. {
  231. if (shard.Session != null)
  232. {
  233. shard.Session.CacheMode = value;
  234. }
  235. else
  236. {
  237. shard.AddOpenSessionEvent(@event);
  238. }
  239. }
  240. }
  241. }
  242. /// <summary>
  243. /// Get the <see cref="ISessionFactory" /> that created this instance.
  244. /// </summary>
  245. public ISessionFactory SessionFactory
  246. {
  247. get { return shardedSessionFactory; }
  248. }
  249. /// <summary>
  250. /// Deprecated.
  251. /// </summary>
  252. public IDbConnection Connection
  253. {
  254. get { throw new InvalidOperationException("On Shards this is deprecated"); }
  255. }
  256. /// <summary>
  257. /// Disconnect the <c>ISession</c> from the current ADO.NET connection.
  258. /// </summary>
  259. /// <remarks>
  260. /// If the connection was obtained by Hibernate, close it or return it to the connection
  261. /// pool. Otherwise return it to the application. This is used by applications which require
  262. /// long transactions.
  263. /// </remarks>
  264. /// <returns>The connection provided by the application or <see langword="null" /></returns>
  265. public IDbConnection Disconnect()
  266. {
  267. foreach (IShard shard in shards)
  268. {
  269. if (shard.Session != null)
  270. {
  271. shard.Session.Disconnect();
  272. }
  273. }
  274. // we do not allow application-supplied connections, so we can always return
  275. // null
  276. return null;
  277. }
  278. /// <summary>
  279. /// Obtain a new ADO.NET connection.
  280. /// </summary>
  281. /// <remarks>
  282. /// This is used by applications which require long transactions
  283. /// </remarks>
  284. public void Reconnect()
  285. {
  286. throw new NotSupportedException();
  287. }
  288. /// <summary>
  289. /// Reconnect to the given ADO.NET connection.
  290. /// </summary>
  291. /// <remarks>This is used by applications which require long transactions</remarks>
  292. /// <param name="connection">An ADO.NET connection</param>
  293. public void Reconnect(IDbConnection connection)
  294. {
  295. throw new NotSupportedException("Cannot reconnect a sharded session");
  296. }
  297. /// <summary>
  298. /// End the <c>ISession</c> by disconnecting from the ADO.NET connection and cleaning up.
  299. /// </summary>
  300. /// <remarks>
  301. /// It is not strictly necessary to <c>Close()</c> the <c>ISession</c> but you must
  302. /// at least <c>Disconnect()</c> it.
  303. /// </remarks>
  304. /// <returns>The connection provided by the application or <see langword="null" /></returns>
  305. public IDbConnection Close()
  306. {
  307. List<Exception> thrown = null;
  308. foreach (IShard shard in shards)
  309. {
  310. if (shard.Session != null)
  311. {
  312. try
  313. {
  314. shard.Session.Close();
  315. }
  316. catch (Exception ex)
  317. {
  318. thrown = thrown ?? new List<Exception>();
  319. thrown.Add(ex);
  320. // we're going to try and close everything that was
  321. // opened
  322. }
  323. }
  324. }
  325. shards.Clear();
  326. shardIdsToShards.Clear();
  327. classesWithoutTopLevelSaveSupport.Clear();
  328. if (thrown != null && !(thrown.Count == 0))
  329. {
  330. // we'll just throw the first one
  331. Exception first = thrown[0];
  332. if (typeof(HibernateException).IsAssignableFrom(first.GetType()))
  333. {
  334. throw (HibernateException)first;
  335. }
  336. throw new HibernateException(first);
  337. }
  338. closed = true;
  339. // TODO what should I return here?
  340. return null;
  341. }
  342. /// <summary>
  343. /// Cancel execution of the current query.
  344. /// </summary>
  345. /// <remarks>
  346. /// May be called from one thread to stop execution of a query in another thread.
  347. /// Use with care!
  348. /// </remarks>
  349. public void CancelQuery()
  350. {
  351. // cancel across all shards
  352. foreach (IShard shard in shards)
  353. {
  354. if (shard.Session != null)
  355. {
  356. shard.Session.CancelQuery();
  357. }
  358. }
  359. }
  360. /// <summary>
  361. /// Is the <c>ISession</c> still open?
  362. /// </summary>
  363. public bool IsOpen
  364. {
  365. get
  366. {
  367. // one open session means the sharded session is open
  368. foreach(IShard shard in shards)
  369. {
  370. if(shard.Session != null && shard.Session.IsOpen)
  371. {
  372. return true;
  373. }
  374. }
  375. return !closed;
  376. }
  377. }
  378. /// <summary>
  379. /// Is the <c>ISession</c> currently connected?
  380. /// </summary>
  381. public bool IsConnected
  382. {
  383. get
  384. {
  385. // one connected shard means the session as a whole is connected
  386. foreach (IShard shard in shards)
  387. {
  388. if (shard.Session != null)
  389. {
  390. if (shard.Session.IsConnected)
  391. {
  392. return true;
  393. }
  394. }
  395. }
  396. return false;
  397. }
  398. }
  399. /// <summary>
  400. /// Does this <c>ISession</c> contain any changes which must be
  401. /// synchronized with the database? Would any SQL be executed if
  402. /// we flushed this session?
  403. /// </summary>
  404. public bool IsDirty()
  405. {
  406. // one dirty shard is all it takes
  407. foreach (IShard shard in shards)
  408. {
  409. if (shard.Session != null)
  410. if (shard.Session.IsDirty())
  411. return true;
  412. }
  413. return false;
  414. }
  415. /// <summary>
  416. /// Return the identifier of an entity instance cached by the <c>ISession</c>
  417. /// </summary>
  418. /// <remarks>
  419. /// Throws an exception if the instance is transient or associated with a different
  420. /// <c>ISession</c>
  421. /// </remarks>
  422. /// <param name="obj">a persistent instance</param>
  423. /// <returns>the identifier</returns>
  424. public object GetIdentifier(object obj)
  425. {
  426. foreach (IShard shard in shards)
  427. {
  428. if (shard.Session != null)
  429. {
  430. try
  431. {
  432. return shard.Session.GetIdentifier(obj);
  433. }
  434. catch (TransientObjectException e)
  435. {
  436. // Object is transient or is not associated with this session.
  437. }
  438. }
  439. }
  440. throw new TransientObjectException("Instance is transient or associated with a defferent Session");
  441. }
  442. /// <summary>
  443. /// Is this instance associated with this Session?
  444. /// </summary>
  445. /// <param name="obj">an instance of a persistent class</param>
  446. /// <returns>true if the given instance is associated with this Session</returns>
  447. public bool Contains(object obj)
  448. {
  449. foreach (IShard shard in shards)
  450. {
  451. if (shard.Session != null)
  452. if (shard.Session.Contains(obj))
  453. return true;
  454. }
  455. return false;
  456. }
  457. /// <summary>
  458. /// Remove this instance from the session cache.
  459. /// </summary>
  460. /// <remarks>
  461. /// Changes to the instance will not be synchronized with the database.
  462. /// This operation cascades to associated instances if the association is mapped
  463. /// with <c>cascade="all"</c> or <c>cascade="all-delete-orphan"</c>.
  464. /// </remarks>
  465. /// <param name="obj">a persistent instance</param>
  466. public void Evict(object obj)
  467. {
  468. foreach (IShard shard in shards)
  469. {
  470. if (shard.Session != null)
  471. {
  472. shard.Session.Evict(obj);
  473. }
  474. }
  475. }
  476. /// <summary>
  477. /// Return the persistent instance of the given entity class with the given identifier,
  478. /// obtaining the specified lock mode.
  479. /// </summary>
  480. /// <param name="clazz">A persistent class</param>
  481. /// <param name="id">A valid identifier of an existing persistent instance of the class</param>
  482. /// <param name="lockMode">The lock level</param>
  483. /// <returns>the persistent instance</returns>
  484. public object Load(System.Type clazz, object id, LockMode lockMode)
  485. {
  486. IList<ShardId> shardIds = SelectShardIdsFromShardResolutionStrategyData(
  487. new ShardResolutionStrategyDataImpl(clazz, id));
  488. if (shardIds.Count == 1)
  489. {
  490. return shardIdsToShards[shardIds[0]].EstablishSession().Load(clazz, id, lockMode);
  491. }
  492. else
  493. {
  494. Object result = Get(clazz, id, lockMode);
  495. if (result == null)
  496. {
  497. shardedSessionFactory.EntityNotFoundDelegate.HandleEntityNotFound(clazz.Name, id);
  498. }
  499. return result;
  500. }
  501. }
  502. public object Load(string entityName, object id, LockMode lockMode)
  503. {
  504. IList<ShardId> shardIds =
  505. SelectShardIdsFromShardResolutionStrategyData(new ShardResolutionStrategyDataImpl(entityName, id));
  506. if(shardIds.Count == 1)
  507. {
  508. return shardIdsToShards[shardIds[0]].EstablishSession().Load(entityName, id, lockMode);
  509. }
  510. object result = Get(entityName, id, lockMode);
  511. if(result == null)
  512. {
  513. shardedSessionFactory.EntityNotFoundDelegate.HandleEntityNotFound(entityName, id);
  514. }
  515. return result;
  516. }
  517. private object Get(string entityName, object id, LockMode mode)
  518. {
  519. IShardOperation<object> shardOp = new GetShardOperationByEntityNameIdAndLockMode(entityName, (ISerializable) id, mode);
  520. // we're not letting people customize shard selection by lockMode
  521. return ApplyGetOperation(shardOp, new ShardResolutionStrategyDataImpl(entityName, id));
  522. }
  523. /// <summary>
  524. /// Return the persistent instance of the given entity class with the given identifier,
  525. /// assuming that the instance exists.
  526. /// </summary>
  527. /// <remarks>
  528. /// You should not use this method to determine if an instance exists (use a query or
  529. /// <see cref="ISession.Get(Type,object)" /> instead). Use this only to retrieve an instance
  530. /// that you assume exists, where non-existence would be an actual error.
  531. /// </remarks>
  532. /// <param name="clazz">A persistent class</param>
  533. /// <param name="id">A valid identifier of an existing persistent instance of the class</param>
  534. /// <returns>The persistent instance or proxy</returns>
  535. public object Load(System.Type clazz, object id)
  536. {
  537. IList<ShardId> shardIds = SelectShardIdsFromShardResolutionStrategyData(new
  538. ShardResolutionStrategyDataImpl(clazz, id));
  539. if (shardIds.Count == 1)
  540. {
  541. return shardIdsToShards[shardIds[0]].EstablishSession().Load(clazz, id);
  542. }
  543. Object result = this.Get(clazz, id);
  544. if (result == null)
  545. {
  546. this.shardedSessionFactory.EntityNotFoundDelegate.HandleEntityNotFound(clazz.Name, id);
  547. }
  548. return result;
  549. }
  550. /// <summary>
  551. /// Return the persistent instance of the given entity class with the given identifier,
  552. /// obtaining the specified lock mode.
  553. /// </summary>
  554. /// <typeparam name="T">A persistent class</typeparam>
  555. /// <param name="id">A valid identifier of an existing persistent instance of the class</param>
  556. /// <param name="lockMode">The lock level</param>
  557. /// <returns>the persistent instance</returns>
  558. public T Load<T>(object id, LockMode lockMode)
  559. {
  560. return (T) Load(typeof (T), id, lockMode);
  561. }
  562. /// <summary>
  563. /// Return the persistent instance of the given entity class with the given identifier,
  564. /// assuming that the instance exists.
  565. /// </summary>
  566. /// <remarks>
  567. /// You should not use this method to determine if an instance exists (use a query or
  568. /// <see cref="ISession.Get{T}(object)" /> instead). Use this only to retrieve an instance that you
  569. /// assume exists, where non-existence would be an actual error.
  570. /// </remarks>
  571. /// <typeparam name="T">A persistent class</typeparam>
  572. /// <param name="id">A valid identifier of an existing persistent instance of the class</param>
  573. /// <returns>The persistent instance or proxy</returns>
  574. public T Load<T>(object id)
  575. {
  576. return (T) Load(typeof (T), id);
  577. }
  578. public object Load(string entityName, object id)
  579. {
  580. IList<ShardId> shardIds = SelectShardIdsFromShardResolutionStrategyData(new
  581. ShardResolutionStrategyDataImpl(entityName, id));
  582. if (shardIds.Count == 1)
  583. {
  584. return shardIdsToShards[shardIds[0]].EstablishSession().Load(entityName, id);
  585. }
  586. else
  587. {
  588. Object result = Get(entityName, id);
  589. if (result == null)
  590. {
  591. shardedSessionFactory.EntityNotFoundDelegate.HandleEntityNotFound(entityName, id);
  592. }
  593. return result;
  594. }
  595. }
  596. /// <summary>
  597. /// Read the persistent state associated with the given identifier into the given transient
  598. /// instance.
  599. /// </summary>
  600. /// <param name="obj">An "empty" instance of the persistent class</param>
  601. /// <param name="id">A valid identifier of an existing persistent instance of the class</param>
  602. public void Load(object obj, object id)
  603. {
  604. IList<ShardId> shardIds =
  605. SelectShardIdsFromShardResolutionStrategyData(new ShardResolutionStrategyDataImpl(obj.GetType(), id));
  606. if (shardIds.Count == 1)
  607. {
  608. shardIdsToShards[shardIds[0]].EstablishSession().Load(obj, id);
  609. }
  610. else
  611. {
  612. object result = Get(obj.GetType(), id);
  613. if (result == null)
  614. {
  615. shardedSessionFactory.EntityNotFoundDelegate.HandleEntityNotFound(obj.GetType().Name, id);
  616. }
  617. else
  618. {
  619. IShard objectShard = GetShardForObject(result, ShardIdListToShardList(shardIds));
  620. Evict(result);
  621. objectShard.EstablishSession().Load(obj, id);
  622. }
  623. }
  624. }
  625. /// <summary>
  626. /// Persist all reachable transient objects, reusing the current identifier
  627. /// values. Note that this will not trigger the Interceptor of the Session.
  628. /// </summary>
  629. /// <param name="obj"></param>
  630. /// <param name="replicationMode"></param>
  631. public void Replicate(object obj, ReplicationMode replicationMode)
  632. {
  633. Replicate(null, obj, replicationMode);
  634. }
  635. /// <summary>
  636. ///
  637. /// </summary>
  638. /// <param name="entityName"></param>
  639. /// <param name="obj"></param>
  640. /// <param name="replicationMode"></param>
  641. public void Replicate(string entityName, object obj, ReplicationMode replicationMode)
  642. {
  643. ISerializable id = ExtractId(obj);
  644. IList<ShardId> shardIds =
  645. SelectShardIdsFromShardResolutionStrategyData(new ShardResolutionStrategyDataImpl(obj.GetType(), id));
  646. if(shardIds.Count == 1)
  647. {
  648. CurrentSubgraphShardId = shardIds[0];
  649. shardIdsToShards[shardIds[0]].EstablishSession().Replicate(entityName, obj, replicationMode);
  650. }
  651. else
  652. {
  653. object result = null;
  654. if(id != null)
  655. {
  656. result = Get(obj.GetType(), id);
  657. }
  658. if(result == null)
  659. {
  660. ShardId shardId = SelectShardIdForNewObject(obj);
  661. CurrentSubgraphShardId = shardId;
  662. shardIdsToShards[shardId].EstablishSession().Replicate(entityName, obj, replicationMode);
  663. }
  664. else
  665. {
  666. IShard objectShard = GetShardForObject(result, ShardIdListToShardList(shardIds));
  667. Evict(result);
  668. objectShard.EstablishSession().Replicate(entityName, obj, replicationMode);
  669. }
  670. }
  671. }
  672. ISerializable ExtractId(object obj)
  673. {
  674. IClassMetadata cmd = shardedSessionFactory.GetClassMetadata(obj.GetType());
  675. // I'm just guessing about the EntityMode
  676. return (ISerializable) cmd.GetIdentifier(obj, EntityMode.Poco);
  677. }
  678. /// <summary>
  679. /// Persist the given transient instance, first assigning a generated identifier.
  680. /// </summary>
  681. /// <remarks>
  682. /// Save will use the current value of the identifier property if the <c>Assigned</c>
  683. /// generator is used.
  684. /// </remarks>
  685. /// <param name="obj">A transient instance of a persistent class</param>
  686. /// <returns>The generated identifier</returns>
  687. public object Save(object obj)
  688. {
  689. return Save(null, obj);
  690. }
  691. public object Save(string entityName, object obj)
  692. {
  693. // TODO: what if we have detached instance?
  694. ShardId shardId = GetShardIdForObject(obj);
  695. if (shardId == null)
  696. {
  697. shardId = SelectShardIdForNewObject(obj);
  698. }
  699. Preconditions.CheckNotNull(shardId);
  700. SetCurrentSubgraphShardId(shardId);
  701. log.Debug(String.Format("Saving object of type {0} to shard {1}", obj.GetType(), shardId));
  702. return shardIdsToShards[shardId].EstablishSession().Save(entityName, obj);
  703. }
  704. public static void SetCurrentSubgraphShardId(ShardId shardId)
  705. {
  706. currentSubgraphShardId = shardId;
  707. }
  708. /*
  709. * We already know that we don't have a shardId locked in for this session,
  710. * and we already know that this object can't grab its session from some
  711. * other object (we looked). If this class is in the set of classes
  712. * that don't support top-level saves, it's an error.
  713. * This is to prevent clients from accidentally splitting their object graphs
  714. * across multiple shards.
  715. */
  716. private void CheckForUnsupportedToplevelSave(System.Type clazz)
  717. {
  718. if(classesWithoutTopLevelSaveSupport.Contains(clazz))
  719. {
  720. string msg = string.Format("Attempt to save object of type {0} as top-level object", clazz.Name);
  721. log.Error(msg);
  722. throw new HibernateException(msg);
  723. }
  724. }
  725. ShardId SelectShardIdForNewObject(object obj)
  726. {
  727. if (lockedShardId != null)
  728. {
  729. return lockedShardId;
  730. }
  731. ShardId shardId;
  732. /*
  733. * Someone is trying to save this object, and that's wonderful, but if
  734. * this object references or is referenced by any other objects that have already been
  735. * associated with a session it's important that this object end up
  736. * associated with the same session. In order to make sure that happens,
  737. * we're going to look at the metadata for this object and see what
  738. * references we have, and then use those to determine the proper shard.
  739. * If we can't find any references we'll leave it up to the shard selection
  740. * strategy.
  741. */
  742. shardId = GetShardIdOfRelatedObject(obj);
  743. if(shardId == null)
  744. {
  745. CheckForUnsupportedToplevelSave(obj.GetType());
  746. shardId = shardStrategy.ShardSelectionStrategy.SelectShardIdForNewObject(obj);
  747. }
  748. // lock has been requested but shard has not yet been selected - lock it in
  749. if(lockedShard)
  750. {
  751. lockedShardId = shardId;
  752. }
  753. log.Debug(string.Format("Selected shard {0} for object of type {1}", shardId.Id, obj.GetType().Name));
  754. return shardId;
  755. }
  756. /**
  757. * TODO(maxr) I can see this method benefitting from a cache that lets us quickly
  758. * see which properties we might need to look at.
  759. */
  760. ShardId GetShardIdOfRelatedObject(object obj)
  761. {
  762. IClassMetadata cmd = GetClassMetadata(obj.GetType());
  763. var types = cmd.GetPropertyValues(obj, EntityMode.Poco); // this wasn't in java null, EntityMode.Poco
  764. // TODO() fix hard-coded entity mode
  765. object[] values = cmd.GetPropertyValues(obj, EntityMode.Poco);
  766. ShardId shardId = null;
  767. List<Collection<Object>> collections = null;
  768. foreach (KeyValuePair<IType, object> pair in CrossShardRelationshipDetectingInterceptor.BuildListOfAssociations(new IType[] {}, new object[] {}))//types, values
  769. {
  770. if (pair.Key.IsCollectionType) //pair.getFirst().isCollectionType()
  771. {
  772. /**
  773. * collection types are more expensive to evaluate (might involve
  774. * lazy-loading the contents of the collection from the db), so
  775. * let's hold off until the end on the chance that we can fail
  776. * quickly.
  777. */
  778. if (collections == null)
  779. {
  780. collections = new List<Collection<object>>();
  781. }
  782. var coll = (Collection<Object>)pair.Value;
  783. collections.Add(coll);
  784. }
  785. else
  786. {
  787. shardId = CheckForConflictingShardId(shardId, obj.GetType(), pair.Value);
  788. /**
  789. * if we're not checking for different shards, return as soon as we've
  790. * got one
  791. */
  792. if (shardId != null && !checkAllAssociatedObjectsForDifferentShards)
  793. {
  794. return shardId;
  795. }
  796. }
  797. }
  798. if (collections != null)
  799. {
  800. foreach (Object collEntry in Iterables.Concatenation(collections))
  801. {
  802. shardId = CheckForConflictingShardId(shardId, obj.GetType(), collEntry);
  803. if (shardId != null && !checkAllAssociatedObjectsForDifferentShards)
  804. {
  805. /**
  806. * if we're not checking for different shards, return as soon as we've
  807. * got one
  808. */
  809. return shardId;
  810. }
  811. }
  812. }
  813. return shardId;
  814. }
  815. ShardId CheckForConflictingShardId(ShardId existingShardId, System.Type newObjectClass, Object associatedObject)
  816. {
  817. ShardId localShardId = GetShardIdForObject(associatedObject);
  818. if (localShardId != null)
  819. {
  820. if (existingShardId == null)
  821. {
  822. existingShardId = localShardId;
  823. }
  824. else if (!localShardId.Equals(existingShardId))
  825. {
  826. /*readonly*/
  827. string msg = string.Format(
  828. "Object of type {0} is on shard {1} but an associated object of type {2} is on shard {3}.",
  829. newObjectClass.Name,
  830. existingShardId.Id,
  831. associatedObject.GetType().Name,
  832. localShardId.Id);
  833. log.Error(msg);
  834. throw new CrossShardAssociationException(msg);
  835. }
  836. }
  837. return existingShardId;
  838. }
  839. IClassMetadata GetClassMetadata(System.Type clazz)
  840. {
  841. return GetShardedSessionFactory().GetClassMetadata(clazz);
  842. }
  843. private IShardedSessionFactoryImplementor GetShardedSessionFactory()
  844. {
  845. return shardedSessionFactory; // why when I set as property expects a Delegate?
  846. }
  847. /// <summary>
  848. /// Persist the given transient instance, using the given identifier.
  849. /// </summary>
  850. /// <param name="obj">A transient instance of a persistent class</param>
  851. /// <param name="id">An unused valid identifier</param>
  852. public void Save(object obj, object id)
  853. {
  854. throw new NotSupportedException();
  855. }
  856. /// <summary>
  857. /// Either <c>Save()</c> or <c>Update()</c> the given instance, depending upon the value of
  858. /// its identifier property.
  859. /// </summary>
  860. /// <remarks>
  861. /// By default the instance is always saved. This behaviour may be adjusted by specifying
  862. /// an <c>unsaved-value</c> attribute of the identifier property mapping
  863. /// </remarks>
  864. /// <param name="obj">A transient instance containing new or updated state</param>
  865. public void SaveOrUpdate(object obj)
  866. {
  867. ApplySaveOrUpdateOperation(new SaveOrUpdateSimple(), obj);
  868. }
  869. void ApplySaveOrUpdateOperation(ISaveOrUpdateOperation op, object obj)
  870. {
  871. ShardId shardId = GetShardIdForObject(obj);
  872. if(shardId != null)
  873. {
  874. // attached object
  875. op.SaveOrUpdate(shardIdsToShards[shardId], obj);
  876. return;
  877. }
  878. IList<IShard> potentialShards = DetermineShardsObjectViaResolutionStrategy(obj);
  879. if(potentialShards.Count == 1)
  880. {
  881. op.SaveOrUpdate(potentialShards[0], obj);
  882. return;
  883. }
  884. /**
  885. * Too bad, we've got a detached object that could be on more than 1 shard.
  886. * The only safe way to handle this is to try and lookup the object, and if
  887. * it exists, do a merge, and if it doesn't, do a save.
  888. */
  889. ISerializable id = ExtractId(obj);
  890. if(id != null)
  891. {
  892. object persistent = Get(obj.GetType(), id);
  893. if(persistent != null)
  894. {
  895. shardId = GetShardIdForObject(persistent);
  896. }
  897. }
  898. if(shardId != null)
  899. {
  900. op.Merge(shardIdsToShards[shardId], obj);
  901. }
  902. else
  903. {
  904. Save(obj);
  905. }
  906. }
  907. IList<IShard> DetermineShardsObjectViaResolutionStrategy(object obj)
  908. {
  909. ISerializable id = ExtractId(obj);
  910. if(id == null)
  911. {
  912. return new List<IShard>();
  913. }
  914. var srsd = new ShardResolutionStrategyDataImpl(obj.GetType(), id);
  915. IList<ShardId> shardIds = SelectShardIdsFromShardResolutionStrategyData(srsd);
  916. return ShardIdListToShardList(shardIds);
  917. }
  918. public void SaveOrUpdate(string entityName, object obj)
  919. {
  920. ISaveOrUpdateOperation so = new SaveOrUpdateWithEntityName(entityName);
  921. ApplySaveOrUpdateOperation(so, obj);
  922. }
  923. /// <summary>
  924. /// Update the persistent instance with the identifier of the given transient instance.
  925. /// </summary>
  926. /// <remarks>
  927. /// If there is a persistent instance with the same identifier, an exception is thrown. If
  928. /// the given transient instance has a <see langword="null" /> identifier, an exception will be thrown.
  929. /// </remarks>
  930. /// <param name="obj">A transient instance containing updated state</param>
  931. public void Update(object obj)
  932. {
  933. IUpdateOperation updateOperation = new UpdateOperationSimple();
  934. ApplyUpdateOperation(updateOperation, obj);
  935. }
  936. private void ApplyUpdateOperation(IUpdateOperation updateOperation, object obj)
  937. {
  938. ShardId shardId = GetShardIdForObject(obj);
  939. if(shardId != null)
  940. {
  941. // attached object
  942. updateOperation.Update(shardIdsToShards[shardId], obj);
  943. return;
  944. }
  945. IList<IShard> potentialShards = DetermineShardsObjectViaResolutionStrategy(obj);
  946. if(potentialShards.Count == 1)
  947. {
  948. updateOperation.Update(potentialShards[0], obj);
  949. return;
  950. }
  951. /**
  952. * Too bad, we've got a detached object that could be on more than 1 shard.
  953. * The only safe way to perform the update is to load the object and then
  954. * do a merge.
  955. */
  956. ISerializable id = ExtractId(obj);
  957. if(id != null)
  958. {
  959. object persistent = Get(obj.GetType(), id);
  960. if(persistent != null)
  961. {
  962. shardId = GetShardIdForObject(persistent);
  963. }
  964. }
  965. if(shardId == null)
  966. {
  967. /**
  968. * This is an error condition. In order to provide the same behavior
  969. * as a non-sharded session we're just going to dispatch the update
  970. * to a random shard (we know it will fail because either we don't have
  971. * an id or the lookup returned).
  972. */
  973. updateOperation.Update(Shards[0], obj);
  974. // this call may succeed but the commit will fail
  975. }
  976. else
  977. {
  978. updateOperation.Merge(shardIdsToShards[shardId], obj);
  979. }
  980. }
  981. /// <summary>
  982. /// Update the persistent state associated with the given identifier.
  983. /// </summary>
  984. /// <remarks>
  985. /// An exception is thrown if there is a persistent instance with the same identifier
  986. /// in the current session.
  987. /// </remarks>
  988. /// <param name="obj">A transient instance containing updated state</param>
  989. /// <param name="id">Identifier of persistent instance</param>
  990. public void Update(object obj, object id)
  991. {
  992. throw new NotSupportedException();
  993. }
  994. public void Update(string entityName, object obj)
  995. {
  996. IUpdateOperation updateOperation = new UpdateOperationWithEntityName(entityName);
  997. ApplyUpdateOperation(updateOperation, obj);
  998. }
  999. public object Merge(object obj)
  1000. {
  1001. return Merge(null, obj);
  1002. }
  1003. public object Merge(string entityName, object obj)
  1004. {
  1005. ISerializable id = ExtractId(obj);
  1006. IList<ShardId> shardIds =
  1007. SelectShardIdsFromShardResolutionStrategyData(new ShardResolutionStrategyDataImpl(obj.GetType(), id));
  1008. if(shardIds.Count == 1)
  1009. {
  1010. SetCurrentSubgraphShardId(shardIds[0]);
  1011. return shardIdsToShards[shardIds[0]].EstablishSession().Merge(entityName, obj);
  1012. }
  1013. object result = null;
  1014. if(id != null)
  1015. {
  1016. result = Get(obj.GetType(), id);
  1017. }
  1018. if(result == null)
  1019. {
  1020. ShardId shardId = SelectShardIdForNewObject(obj);
  1021. SetCurrentSubgraphShardId(shardId);
  1022. return shardIdsToShards[shardId].EstablishSession().Merge(entityName, obj);
  1023. }
  1024. IShard objectShard = GetShardForObject(result, ShardIdListToShardList(shardIds));
  1025. return objectShard.EstablishSession().Merge(entityName, obj);
  1026. }
  1027. public void Persist(object obj)
  1028. {
  1029. Persist(null, obj);
  1030. }
  1031. public void Persist(string entityName, object obj)
  1032. {
  1033. //TODO: what if we have detached object?
  1034. ShardId shardId = GetShardIdForObject(obj);
  1035. if(shardId == null)
  1036. {
  1037. shardId = SelectShardIdForNewObject(obj);
  1038. }
  1039. Preconditions.CheckNotNull(shardId);
  1040. SetCurrentSubgraphShardId(shardId);
  1041. log.Debug(string.Format("Persisting object of type {0} to shard {1}", obj.GetType(), shardId));
  1042. shardIdsToShards[shardId].EstablishSession().Persist(entityName, obj);
  1043. }
  1044. /// <summary>
  1045. /// Copy the state of the given object onto the persistent object with the same
  1046. /// identifier. If there is no persistent instance currently associated with
  1047. /// the session, it will be loaded. Return the persistent instance. If the
  1048. /// given instance is unsaved or does not exist in the database, save it and
  1049. /// return it as a newly persistent instance. Otherwise, the given instance
  1050. /// does not become associated with the session.
  1051. /// </summary>
  1052. /// <param name="obj">a transient instance with state to be copied</param>
  1053. /// <returns>an updated persistent instance</returns>
  1054. public object SaveOrUpdateCopy(object obj)
  1055. {
  1056. throw new NotSupportedException();
  1057. }
  1058. /// <summary>
  1059. /// Copy the state of the given object onto the persistent object with the
  1060. /// given identifier. If there is no persistent instance currently associated
  1061. /// with the session, it will be loaded. Return the persistent instance. If
  1062. /// there is no database row with the given identifier, save the given instance
  1063. /// and return it as a newly persistent instance. Otherwise, the given instance
  1064. /// does not become associated with the session.
  1065. /// </summary>
  1066. /// <param name="obj">a persistent or transient instance with state to be copied</param>
  1067. /// <param name="id">the identifier of the instance to copy to</param>
  1068. /// <returns>an updated persistent instance</returns>
  1069. public object SaveOrUpdateCopy(object obj, object id)
  1070. {
  1071. throw new NotSupportedException();
  1072. }
  1073. /// <summary>
  1074. /// Remove a persistent instance from the datastore.
  1075. /// </summary>
  1076. /// <remarks>
  1077. /// The argument may be an instance associated with the receiving <c>ISession</c> or a
  1078. /// transient instance with an identifier associated with existing persistent state.
  1079. /// </remarks>
  1080. /// <param name="obj">The instance to be removed</param>
  1081. public void Delete(object obj)
  1082. {
  1083. IDeleteOperation deleteOperation = new DeleteOperationSimple();
  1084. ApplyDeleteOperation(deleteOperation, obj);
  1085. }
  1086. public void Delete(string entityName, object obj)
  1087. {
  1088. IDeleteOperation deleteOperation = new DeleteOperationWithEntityName(entityName);
  1089. ApplyDeleteOperation(deleteOperation, obj);
  1090. }
  1091. private void ApplyDeleteOperation(IDeleteOperation deleteOperation, object obj)
  1092. {
  1093. ShardId shardId = GetShardIdForObject(obj);
  1094. if (shardId != null)
  1095. {
  1096. // attached object
  1097. deleteOperation.Delete(shardIdsToShards[shardId], obj);
  1098. return;
  1099. }
  1100. /**
  1101. * Detached object.
  1102. * We can't just try to delete on each shard because if you have an
  1103. * object associated with Session x and you try to delete that object in
  1104. * Session y, and if that object has persistent collections, Hibernate will
  1105. * blow up because it will try to associate the persistent collection with
  1106. * a different Session as part of the cascade. In order to avoid this we
  1107. * need to be precise about the shard on which we perform the delete.
  1108. *
  1109. * First let's see if we can derive the shard just from the object's id.
  1110. */
  1111. IList<IShard> potentialShards = DetermineShardsObjectViaResolutionStrategy(obj);
  1112. if(potentialShards.Count == 1)
  1113. {
  1114. deleteOperation.Delete(potentialShards[0], obj);
  1115. return;
  1116. }
  1117. /**
  1118. * Too bad, we've got a detached object that could be on more than 1 shard.
  1119. * The only safe way to perform the delete is to load the object before
  1120. * deleting.
  1121. */
  1122. object persistent = Get(obj.GetType(), ExtractId(obj));
  1123. shardId = GetShardIdForObject(persistent);
  1124. deleteOperation.Delete(shardIdsToShards[shardId], persistent);
  1125. }
  1126. /// <summary>
  1127. /// Execute a query
  1128. /// </summary>
  1129. /// <param name="query">A query expressed in Hibernate's query language</param>
  1130. /// <returns>A distinct list of instances</returns>
  1131. /// <remarks>See <see cref="IQuery.List()"/> for implications of <c>cache</c> usage.</remarks>
  1132. public IList Find(string query)
  1133. {
  1134. throw new NotSupportedException();
  1135. }
  1136. /// <summary>
  1137. /// Execute a query, binding a value to a "?" parameter in the query string.
  1138. /// </summary>
  1139. /// <param name="query">The query string</param>
  1140. /// <param name="value">A value to be bound to a "?" placeholder</param>
  1141. /// <param name="type">The Hibernate type of the value</param>
  1142. /// <returns>A distinct list of instances</returns>
  1143. /// <remarks>See <see cref="IQuery.List()"/> for implications of <c>cache</c> usage.</remarks>
  1144. public IList Find(string query, object value, IType type)
  1145. {
  1146. throw new NotSupportedException();
  1147. }
  1148. /// <summary>
  1149. /// Execute a query, binding an array of values to a "?" parameters in the query string.
  1150. /// </summary>
  1151. /// <param name="query">The query string</param>
  1152. /// <param name="values">An array of values to be bound to the "?" placeholders</param>
  1153. /// <param name="types">An array of Hib…

Large files files are truncated, but you can click here to view the full file