1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908
|
/*
* Copyright (c) 1998, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
// 05/28/2008-1.0M8 Andrei Ilitchev
// - 224964: Provide support for Proxy Authentication through JPA.
// Added a new constructor that takes Properties.
// 14/05/2012-2.4 Guy Pelletier
// - 376603: Provide for table per tenant support for multitenant applications
// 08/11/2012-2.5 Guy Pelletier
// - 393867: Named queries do not work when using EM level Table Per Tenant Multitenancy.
// 09/03/2015 - Will Dazey
// - 456067 : Added support for defining query timeout units
package org.eclipse.persistence.sessions.server;
import java.util.*;
import java.io.*;
import org.eclipse.persistence.platform.server.ServerPlatform;
import org.eclipse.persistence.queries.*;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.SchemaPerMultitenantPolicy;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.internal.databaseaccess.*;
import org.eclipse.persistence.internal.sequencing.Sequencing;
import org.eclipse.persistence.internal.sequencing.SequencingFactory;
import org.eclipse.persistence.sessions.coordination.CommandManager;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.internal.sessions.*;
import org.eclipse.persistence.sessions.DatabaseLogin;
import org.eclipse.persistence.sessions.SessionProfiler;
import org.eclipse.persistence.internal.sessions.AbstractSession;
/**
* <b>Purpose</b>: Acts as a client to the server session.
* <p>
* <b>Description</b>: This session is brokered by the server session for use in three-tiered applications.
* It is used to store the context of the connection, i.e. the login to be used for this client.
* This allows each client connected to the server to contain its own user login.
* <p>
* <b>Responsibilities</b>:
* <ul>
* <li> Allow units of work to be acquired and pass them the client login's exclusive connection.
* <li> Forward all requests and queries to its parent server session.
* </ul>
* <p>
* This class is an implementation of {@link org.eclipse.persistence.sessions.Session}.
* Please refer to that class for a full API. The public interface should be used.
* @see Server
* @see org.eclipse.persistence.sessions.Session
* @see org.eclipse.persistence.sessions.UnitOfWork
*/
public class ClientSession extends AbstractSession {
protected ServerSession parent;
protected ConnectionPolicy connectionPolicy;
protected Map<String, Accessor> writeConnections;
protected boolean isActive;
protected Sequencing sequencing;
/**
* INTERNAL:
* Create and return a new client session.
*/
public ClientSession(ServerSession parent, ConnectionPolicy connectionPolicy) {
this(parent, connectionPolicy, null);
}
public ClientSession(ServerSession parent, ConnectionPolicy connectionPolicy, Map properties) {
super();
// If we have table per tenant descriptors let's clone the project so
// that we can have a separate jpql parse cache for each tenant.
if (parent.hasTablePerTenantDescriptors() || parent.getProject().getMultitenantPolicy() != null) {
this.project = parent.getProject().clone();
this.project.setJPQLParseCacheMaxSize(parent.getProject().getJPQLParseCache().getMaxSize());
} else {
this.project = parent.getProject();
}
if (connectionPolicy.isUserDefinedConnection()) {
// PERF: project only requires clone if login is different
this.setProject(getProject().clone());
this.setLogin(connectionPolicy.getLogin());
}
if (this.project.getMultitenantPolicy() != null && this.project.getMultitenantPolicy().isSchemaPerMultitenantPolicy()) {
SchemaPerMultitenantPolicy mp = (SchemaPerMultitenantPolicy) this.project.getMultitenantPolicy();
if (mp.shouldUseSharedEMF()) {
//force different login instance
this.setLogin(getLogin().clone());
}
}
this.isLoggingOff = parent.isLoggingOff();
this.isActive = true;
this.externalTransactionController = parent.getExternalTransactionController();
this.parent = parent;
this.connectionPolicy = connectionPolicy;
this.name = parent.getName();
this.profiler = parent.getProfiler();
this.serializer = parent.getSerializer();
this.isInProfile = parent.isInProfile();
this.commitManager = parent.getCommitManager();
this.partitioningPolicy = parent.getPartitioningPolicy();
this.sessionLog = parent.getSessionLog();
if (parent.hasEventManager()) {
this.eventManager = parent.getEventManager().clone(this);
}
this.exceptionHandler = parent.getExceptionHandler();
this.pessimisticLockTimeoutDefault = parent.getPessimisticLockTimeoutDefault();
this.pessimisticLockTimeoutUnitDefault = parent.getPessimisticLockTimeoutUnitDefault();
this.queryTimeoutDefault = parent.getQueryTimeoutDefault();
this.queryTimeoutUnitDefault = parent.getQueryTimeoutUnitDefault();
this.isConcurrent = parent.isConcurrent();
this.shouldOptimizeResultSetAccess = parent.shouldOptimizeResultSetAccess();
this.properties = properties;
this.multitenantContextProperties = parent.getMultitenantContextProperties();
if (this.eventManager != null) {
this.eventManager.postAcquireClientSession();
}
// Copy down the table per tenant queries from the parent. These queries
// must be cloned per client session.
if (parent.hasTablePerTenantQueries()) {
for (DatabaseQuery query : parent.getTablePerTenantQueries()) {
addTablePerTenantQuery((DatabaseQuery) query.clone());
}
}
// If we have table per tenant descriptors, they will need to be
// cloned as we will be changing the descriptors per tenant.
if (parent.hasTablePerTenantDescriptors()) {
this.descriptors = new HashMap<Class, ClassDescriptor>();
this.descriptors.putAll(parent.getDescriptors());
for (ClassDescriptor descriptor : parent.getTablePerTenantDescriptors()) {
ClassDescriptor clonedDescriptor = (ClassDescriptor) descriptor.clone();
addTablePerTenantDescriptor(clonedDescriptor);
this.descriptors.put(clonedDescriptor.getJavaClass(), clonedDescriptor);
}
if (hasProperties()) {
for (Object propertyName : properties.keySet()) {
updateTablePerTenantDescriptors((String) propertyName, properties.get(propertyName));
}
}
} else {
this.descriptors = parent.getDescriptors();
}
incrementProfile(SessionProfiler.ClientSessionCreated);
}
protected ClientSession(org.eclipse.persistence.sessions.Project project) {
super(project);
}
/**
* INTERNAL:
* Called in the end of beforeCompletion of external transaction synchronization listener.
* Close the managed sql connection corresponding to the external transaction
* and releases accessor.
*/
@Override
public void releaseJTSConnection() {
if (hasWriteConnection()) {
for (Accessor accessor : getWriteConnections().values()) {
accessor.closeJTSConnection();
}
releaseWriteConnection();
}
}
/**
* INTERNAL:
* This is internal to the unit of work and should not be called otherwise.
*/
@Override
public void basicCommitTransaction() {
//Only release connection when transaction succeeds.
//If not, connection will be released in rollback.
super.basicCommitTransaction();
// if synchronized then the connection will be released in external transaction callback.
if (hasExternalTransactionController()) {
if(!isSynchronized()) {
releaseJTSConnection();
}
} else {
releaseWriteConnection();
}
}
/**
* INTERNAL:
* This is internal to the unit of work and should not be called otherwise.
*/
@Override
public void basicRollbackTransaction() {
try {
//BUG 2660471: Make sure there is an accessor (moved here from Session)
//BUG 2846785: EXCEPTION THROWN IN PREBEGINTRANSACTION EVENT CAUSES NPE
if (hasWriteConnection()) {
super.basicRollbackTransaction();
}
} finally {
// if synchronized then the connection will be released in external transaction callback.
if (hasExternalTransactionController()) {
if(!isSynchronized()) {
releaseJTSConnection();
}
} else {
releaseWriteConnection();
}
}
}
/**
* INTERNAL:
* Connect the session only (this must be the write connection as the read is shared).
*/
public void connect(Accessor accessor) throws DatabaseException {
accessor.connect(getDatasourceLogin(), this);
}
/**
* INTERNAL:
* Was PUBLIC: customer will be redirected to {@link org.eclipse.persistence.sessions.Session}.
* Return true if the pre-defined query is defined on the session.
*/
@Override
public boolean containsQuery(String queryName) {
boolean containsQuery = getQueries().containsKey(queryName);
if (containsQuery == false) {
containsQuery = this.parent.containsQuery(queryName);
}
return containsQuery;
}
/**
* INTERNAL:
* Disconnect the accessor only (this must be the write connection as the read is shared).
*/
public void disconnect(Accessor accessor) throws DatabaseException {
accessor.disconnect(this);
}
/**
* INTERNAL:
* Execute the call on the correct connection accessor.
* Outside of a transaction the server session's read connection pool is used.
* In side a transaction, or for exclusive sessions the write connection is used.
* For partitioning there may be multiple write connections.
*/
@Override
public Object executeCall(Call call, AbstractRecord translationRow, DatabaseQuery query) throws DatabaseException {
if ((!isInTransaction() || (query.isObjectLevelReadQuery() && ((ObjectLevelReadQuery)query).isReadOnly())) && !isExclusiveIsolatedClientSession() ) {
return this.parent.executeCall(call, translationRow, query);
}
boolean shouldReleaseConnection = false;
if (query.getAccessors() == null) {
// First check for a partitioning policy.
// An exclusive session will always use a single connection once allocated.
if (!hasWriteConnection() || !isExclusiveIsolatedClientSession()) {
Collection<Accessor> accessors = getAccessors(call, translationRow, query);
if (accessors != null && !accessors.isEmpty()) {
query.setAccessors(accessors);
// the session has been already released and this query is likely instantiates a ValueHolder -
// release exclusive connection immediately after the query is executed, otherwise it may never be released.
shouldReleaseConnection = !this.isActive;
}
}
}
if (query.getAccessors() == null) {
// If the connection has not yet been acquired then do it here.
if (!hasWriteConnection()) {
this.parent.acquireClientConnection(this);
// The session has been already released and this query is likely instantiates a ValueHolder -
// release exclusive connection immediately after the query is executed, otherwise it may never be released.
shouldReleaseConnection = !this.isActive;
query.setAccessors(getAccessors());
} else {
// Must use the default write connection if there are multiple connections.
if (!isExclusiveIsolatedClientSession() && this.connectionPolicy.isPooled()) {
Accessor defaultWriteConnection = this.writeConnections.get(this.connectionPolicy.getPoolName());
if (defaultWriteConnection == null) {
// No default connection yet, must acquire it.
this.parent.acquireClientConnection(this);
}
if (this.writeConnections.size() == 1) {
// Connection is the default, just use it.
query.setAccessors(getAccessors());
} else {
List<Accessor> accessors = new ArrayList(1);
accessors.add(defaultWriteConnection);
query.setAccessors(accessors);
}
} else {
query.setAccessors(getAccessors());
}
}
}
Object result = null;
RuntimeException exception = null;
try {
result = basicExecuteCall(call, translationRow, query);
} catch (RuntimeException caughtException) {
exception = caughtException;
} finally {
if (call.isFinished() || exception != null) {
query.setAccessors(null);
// Note that connection could be release only if it has been acquired by the same query,
// that allows to execute other queries from postAcquireConnection / preReleaseConnection events
// without wiping out connection set by the original query or causing stack overflow, see
// bug 299048 - Triggering indirection on closed ExclusiveIsolatedSession may cause exception
if (shouldReleaseConnection && hasWriteConnection()) {
try {
this.parent.releaseClientSession(this);
} catch (RuntimeException releaseException) {
if (exception == null) {
throw releaseException;
}
//else ignore
}
}
} else {
if (query.isObjectLevelReadQuery()) {
((DatabaseCall)call).setHasAllocatedConnection(shouldReleaseConnection);
}
}
if (exception != null) {
throw exception;
}
}
return result;
}
/**
* INTERNAL:
* Release (if required) connection after call.
* @param query
*/
public void releaseConnectionAfterCall(DatabaseQuery query) {
if ((!isInTransaction() || (query.isObjectLevelReadQuery() && ((ObjectLevelReadQuery)query).isReadOnly())) && !isExclusiveIsolatedClientSession() ) {
this.parent.releaseConnectionAfterCall(query);
} else {
if (hasWriteConnection()) {
query.setAccessors(null);
this.parent.releaseClientSession(this);
}
}
}
/**
* INTERNAL:
* Return the write connections if in a transaction.
* These may be empty/null until the first query has been executed inside the transaction.
* This should only be called within a transaction.
* If outside of a transaction it will return null (unless using an exclusive connection).
*/
@Override
public Collection<Accessor> getAccessors() {
if (isInTransaction()) {
if (this.writeConnections == null) {
return null;
}
return this.writeConnections.values();
} else {
return this.accessors;
}
}
/**
* INTERNAL:
* This should normally not be used, getAccessors() should be used to support partitioning.
* To maintain backward compatibility, and to support certain cases that required a default accessor,
* if inside a transaction, then a default connection will be allocated.
* This is required for sequencing, and JPA connection unwrapping, and ordt mappings.
* Outside of a transaction, to maintain backward compatibility the server session's accessor will be returned.
*/
@Override
public Accessor getAccessor() {
Collection<Accessor> accessors = getAccessors();
if ((accessors == null) || accessors.isEmpty()) {
if (isInTransaction()) {
this.parent.acquireClientConnection(this);
accessors = getAccessors();
} else {
return this.parent.getAccessor();
}
}
if (accessors instanceof List) {
return ((List<Accessor>)accessors).get(0);
}
return accessors.iterator().next();
}
/**
* ADVANCED:
* This method will return the connection policy that was used during the
* acquisition of this client session. The properties within the ConnectionPolicy
* may be used when acquiring an exclusive connection for an IsolatedSession.
*/
public ConnectionPolicy getConnectionPolicy() {
return connectionPolicy;
}
/**
* ADVANCED:
* Return all registered descriptors.
*/
public Map<Class, ClassDescriptor> getDescriptors() {
// descriptors from the project may have been modified (for table per
// tenants so make sure to return the updated ones)
if (hasTablePerTenantDescriptors()) {
return this.descriptors;
} else {
return super.getDescriptors();
}
}
/**
* INTERNAL:
* Returns the appropriate IdentityMap session for this descriptor. Sessions can be
* chained and each session can have its own Cache/IdentityMap. Entities can be stored
* at different levels based on Cache Isolation. This method will return the correct Session
* for a particular Entity class based on the Isolation Level and the attributes provided.
* <p>
* @param canReturnSelf true when method calls itself. If the path
* starting at <code>this</code> is acceptable. Sometimes true if want to
* move to the first valid session, i.e. executing on ClientSession when really
* should be on ServerSession.
* @param terminalOnly return the last session in the chain where the Enitity is stored.
* @return Session with the required IdentityMap
*/
@Override
public AbstractSession getParentIdentityMapSession(ClassDescriptor descriptor, boolean canReturnSelf, boolean terminalOnly) {
// Note could return self as ClientSession shares the same identity map
// as parent. This reveals a deep problem, as queries will be cached in
// the Server identity map but executed here using the write connection.
return this.parent.getParentIdentityMapSession(descriptor, canReturnSelf, terminalOnly);
}
/**
* Search for and return the user defined property from this client session, if it not found then search for the property
* from parent.
*/
@Override
public Object getProperty(String name){
Object propertyValue = super.getProperty(name);
if (propertyValue == null) {
propertyValue = this.parent.getProperty(name);
}
return propertyValue;
}
/**
* INTERNAL:
* Gets the session which this query will be executed on.
* Generally will be called immediately before the call is translated,
* which is immediately before session.executeCall.
* <p>
* Since the execution session also knows the correct datasource platform
* to execute on, it is often used in the mappings where the platform is
* needed for type conversion, or where calls are translated.
* <p>
* Is also the session with the accessor. Will return a ClientSession if
* it is in transaction and has a write connection.
* @return a session with a live accessor
* @param query may store session name or reference class for brokers case
*/
@Override
public AbstractSession getExecutionSession(DatabaseQuery query) {
// For CR#4334 if in transaction stay on client session.
// That way client's write accessor will be used for all queries.
// This is to preserve transaction isolation levels.
// For bug 3602222 if a query is executed directly on a client session when
// in transaction, then dirty data could be put in the shared cache for the
// client session uses the identity map of its parent.
// However beginTransaction() is not public API on ClientSession.
// if fix this could add: && (query.getSession() != this).
if (isInTransaction()) {
return this;
}
return this.parent.getExecutionSession(query);
}
/**
* INTERNAL:
* Return the parent.
* This is a server session.
*/
@Override
public ServerSession getParent() {
return parent;
}
/**
* INTERNAL:
* Was PUBLIC: customer will be redirected to {@link org.eclipse.persistence.sessions.Session}.
* Return the query from the session pre-defined queries with the given name.
* This allows for common queries to be pre-defined, reused and executed by name.
*/
@Override
public DatabaseQuery getQuery(String name) {
DatabaseQuery query = super.getQuery(name);
if (query == null) {
query = this.parent.getQuery(name);
}
return query;
}
/**
* INTERNAL:
*/
@Override
public DatabaseQuery getQuery(String name, Vector args) {// CR3716; Predrag;
DatabaseQuery query = super.getQuery(name, args);
if (query == null) {
query = this.parent.getQuery(name, args);
}
return query;
}
/**
* INTERNAL:
* was ADVANCED:
* Creates sequencing object for the session.
* Typically there is no need for the user to call this method -
* it is called from the constructor.
*/
public void initializeSequencing() {
this.sequencing = SequencingFactory.createSequencing(this);
}
/**
* INTERNAL:
* Return the Sequencing object used by the session.
* Lazy init sequencing to defer from client session creation to improve creation performance.
*/
@Override
public Sequencing getSequencing() {
// PERF: lazy init defer from constructor, only created when needed.
if (this.sequencing == null) {
initializeSequencing();
}
return this.sequencing;
}
/**
* INTERNAL:
* Marked internal as this is not customer API but helper methods for
* accessing the server platform from within other sessions types
* (i.e. not DatabaseSession)
*/
@Override
public ServerPlatform getServerPlatform() {
return this.parent.getServerPlatform();
}
/**
* INTERNAL:
* Returns the type of session, its class.
* <p>
* Override to hide from the user when they are using an internal subclass
* of a known class.
* <p>
* A user does not need to know that their UnitOfWork is a
* non-deferred UnitOfWork, or that their ClientSession is an
* IsolatedClientSession.
*/
@Override
public String getSessionTypeString() {
return "ClientSession";
}
/**
* INTERNAL:
* Return the map of write connections.
* Multiple connections can be used for data partitioning and replication.
* The connections are keyed by connection pool name.
*/
public Map<String, Accessor> getWriteConnections() {
if (this.writeConnections == null) {
this.writeConnections = new HashMap(4);
}
return this.writeConnections;
}
/**
* INTERNAL:
* Return the connection to be used for database modification.
*/
public Accessor getWriteConnection() {
if ((this.writeConnections == null) || this.writeConnections.isEmpty()) {
return null;
}
return this.writeConnections.values().iterator().next();
}
/**
* INTERNAL:
* Return if this session has been connected.
*/
public boolean hasWriteConnection() {
if (this.writeConnections == null) {
return false;
}
return !this.writeConnections.isEmpty();
}
/**
* INTERNAL:
* Set up the IdentityMapManager. This method allows subclasses of Session to override
* the default IdentityMapManager functionality.
*/
@Override
public void initializeIdentityMapAccessor() {
this.identityMapAccessor = new ClientSessionIdentityMapAccessor(this);
}
/**
* INTERNAL:
* Was PUBLIC: customer will be redirected to {@link org.eclipse.persistence.sessions.Session}.
* Return if the client session is active (has not been released).
*/
public boolean isActive() {
return isActive;
}
/**
* INTERNAL:
* Return if this session is a client session.
*/
@Override
public boolean isClientSession() {
return true;
}
/**
* INTERNAL:
* Was PUBLIC: customer will be redirected to {@link org.eclipse.persistence.sessions.Session}.
* Return if this session has been connected to the database.
*/
@Override
public boolean isConnected() {
return this.parent.isConnected();
}
/**
* INTERNAL:
* Was PUBLIC: customer will be redirected to {@link org.eclipse.persistence.sessions.Session}.
* Release the client session.
* This releases the client session back to it server.
* Normally this will logout of the client session's connection,
* and allow the client session to garbage collect.
*/
@Override
public void release() throws DatabaseException {
// Clear referencing classes. If this is not done the object is not garbage collected.
for (Map.Entry<Class, ClassDescriptor> entry : getDescriptors().entrySet()) {
entry.getValue().clearReferencingClasses();
}
if (!this.isActive) {
return;
}
if (this.eventManager != null) {
this.eventManager.preReleaseClientSession();
}
//removed is Lazy check as we should always release the connection once
//the client session has been released. It is also required for the
//behavior of a subclass ExclusiveIsolatedClientSession
if (hasWriteConnection()) {
this.parent.releaseClientSession(this);
}
// we are not inactive until the connection is released
this.isActive = false;
log(SessionLog.FINER, SessionLog.CONNECTION, "client_released");
if (this.eventManager != null) {
this.eventManager.postReleaseClientSession();
}
incrementProfile(SessionProfiler.ClientSessionReleased);
}
/**
* INTERNAL:
* A query execution failed due to an invalid query.
* Re-connect and retry the query.
*/
@Override
public Object retryQuery(DatabaseQuery query, AbstractRecord row, DatabaseException databaseException, int retryCount, AbstractSession executionSession) {
// If not in a transaction and has a write connection, must release it if invalid.
getParent().releaseInvalidClientSession(this);
return super.retryQuery(query, row, databaseException, retryCount, executionSession);
}
/**
* INTERNAL:
* This is internal to the unit of work and should not be called otherwise.
*/
protected void releaseWriteConnection() {
if (this.connectionPolicy.isLazy() && hasWriteConnection()) {
this.parent.releaseClientSession(this);
}
}
/**
* INTERNAL:
* Set the connection policy.
*/
public void setConnectionPolicy(ConnectionPolicy connectionPolicy) {
this.connectionPolicy = connectionPolicy;
}
/**
* INTERNAL:
* Set if the client session is active (has not been released).
*/
protected void setIsActive(boolean isActive) {
this.isActive = isActive;
}
/**
* INTERNAL:
* Set the parent.
* This is a server session.
*/
protected void setParent(ServerSession parent) {
this.parent = parent;
}
/**
* INTERNAL:
* Add the connection to the client session.
* Multiple connections are supported to allow data partitioning and replication.
* The accessor is returned, as if detected to be dead it may be replaced.
*/
public Accessor addWriteConnection(String poolName, Accessor writeConnection) {
getWriteConnections().put(poolName, writeConnection);
writeConnection.createCustomizer(this);
//if connection is using external connection pooling then the event will be risen right after it connects.
if (!writeConnection.usesExternalConnectionPooling()) {
postAcquireConnection(writeConnection);
}
// Transactions are lazily started on connections.
if (isInTransaction()) {
basicBeginTransaction(writeConnection);
}
return getWriteConnections().get(poolName);
}
/**
* INTERNAL:
* A begin transaction failed.
* Re-connect and retry the begin transaction.
*/
@Override
public DatabaseException retryTransaction(Accessor writeConnection, DatabaseException databaseException, int retryCount, AbstractSession executionSession) {
if (writeConnection.getPool() == null) {
return super.retryTransaction(writeConnection, databaseException, retryCount, executionSession);
}
String poolName = writeConnection.getPool().getName();
DatabaseLogin login = getLogin();
int count = login.getQueryRetryAttemptCount();
DatabaseException exceptionToThrow = databaseException;
while (retryCount < count) {
getWriteConnections().remove(poolName);
//if connection is using external connection pooling then the event will be risen right after it connects.
if (!writeConnection.usesExternalConnectionPooling()) {
preReleaseConnection(writeConnection);
}
writeConnection.getPool().releaseConnection(writeConnection);
try {
// attempt to reconnect for a certain number of times.
// servers may take some time to recover.
++retryCount;
writeConnection = writeConnection.getPool().acquireConnection();
writeConnection.beginTransaction(this);
//passing the retry count will prevent a runaway retry where
// we can acquire connections but are unable to execute any queries
if (retryCount > 1) {
// We are retrying more than once lets wait to give connection time to restart.
//Give the failover time to recover.
Thread.currentThread().sleep(login.getDelayBetweenConnectionAttempts());
}
getWriteConnections().put(poolName, writeConnection);
writeConnection.createCustomizer(this);
//if connection is using external connection pooling then the event will be risen right after it connects.
if (!writeConnection.usesExternalConnectionPooling()) {
postAcquireConnection(writeConnection);
}
return null;
} catch (DatabaseException ex){
//replace original exception with last exception thrown
//this exception could be a data based exception as opposed
//to a connection exception that needs to go back to the customer.
exceptionToThrow = ex;
} catch (InterruptedException ex) {
//Ignore interrupted exception.
}
}
return exceptionToThrow;
}
/**
* INTERNAL:
* Set the connection to be used for database modification.
*/
public void setWriteConnections(Map<String, Accessor> writeConnections) {
// Clear customizers.
if ((this.writeConnections != null) && (writeConnections == null)) {
for (Accessor accessor : this.writeConnections.values()) {
accessor.releaseCustomizer(this);
}
}
this.writeConnections = writeConnections;
}
/**
* INTERNAL:
* Set the connection to be used for database modification.
*/
public void setWriteConnection(Accessor writeConnection) {
if (writeConnection == null) {
setWriteConnections(null);
return;
}
String poolName = null;
if (writeConnection.getPool() != null) {
poolName = writeConnection.getPool().getName();
} else {
poolName = ServerSession.NOT_POOLED;
}
addWriteConnection(poolName, writeConnection);
}
/**
* INTERNAL:
* Print the connection status with the session.
*/
@Override
public String toString() {
StringWriter writer = new StringWriter();
writer.write(getSessionTypeString());
writer.write("(");
writer.write(String.valueOf(getWriteConnections()));
writer.write(")");
return writer.toString();
}
/**
* INTERNAL:
* Return the manager that allows this processor to receive or propagate commands from/to TopLink cluster
* @see CommandManager
* @return a remote command manager
*/
@Override
public CommandManager getCommandManager() {
return this.parent.getCommandManager();
}
/**
* INTERNAL:
* Return whether changes should be propagated to TopLink cluster. This is one of the required
* cache synchronization setting
*/
@Override
public boolean shouldPropagateChanges() {
return this.parent.shouldPropagateChanges();
}
/**
* INTERNAL:
* Release the cursor query's connection.
*/
@Override
public void releaseReadConnection(Accessor connection) {
// If the cursor's connection is the write connection, then do not release it.
if ((this.writeConnections != null) && this.writeConnections.containsValue(connection)) {
return;
}
//bug 4668234 -- used to only release connections on server sessions but should always release
this.parent.releaseReadConnection(connection);
}
/**
* INTERNAL:
* This method is called in case externalConnectionPooling is used.
* If returns true, accessor used by the session keeps its
* connection open until released by the session.
*/
@Override
public boolean isExclusiveConnectionRequired() {
return !this.connectionPolicy.isLazy && isActive();
}
}
|