001/*- 002 * #%L 003 * HAPI FHIR Storage api 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.dao.tx; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.interceptor.api.HookParams; 024import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; 025import ca.uhn.fhir.interceptor.api.Pointcut; 026import ca.uhn.fhir.interceptor.model.RequestPartitionId; 027import ca.uhn.fhir.jpa.api.model.ResourceVersionConflictResolutionStrategy; 028import ca.uhn.fhir.jpa.dao.DaoFailureUtil; 029import ca.uhn.fhir.jpa.model.config.PartitionSettings; 030import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; 031import ca.uhn.fhir.rest.api.server.RequestDetails; 032import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; 033import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; 034import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 035import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; 036import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 037import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster; 038import ca.uhn.fhir.util.ICallable; 039import ca.uhn.fhir.util.SleepUtil; 040import com.google.common.annotations.VisibleForTesting; 041import jakarta.annotation.Nonnull; 042import jakarta.annotation.Nullable; 043import org.apache.commons.lang3.Validate; 044import org.apache.commons.lang3.exception.ExceptionUtils; 045import org.hibernate.exception.ConstraintViolationException; 046import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049import org.springframework.beans.factory.annotation.Autowired; 050import org.springframework.dao.DataIntegrityViolationException; 051import org.springframework.dao.PessimisticLockingFailureException; 052import org.springframework.orm.ObjectOptimisticLockingFailureException; 053import org.springframework.transaction.PlatformTransactionManager; 054import org.springframework.transaction.TransactionStatus; 055import org.springframework.transaction.annotation.Isolation; 056import org.springframework.transaction.annotation.Propagation; 057import org.springframework.transaction.support.TransactionCallback; 058import org.springframework.transaction.support.TransactionCallbackWithoutResult; 059import org.springframework.transaction.support.TransactionOperations; 060import org.springframework.transaction.support.TransactionSynchronizationManager; 061import org.springframework.transaction.support.TransactionTemplate; 062 063import java.util.Objects; 064import java.util.concurrent.Callable; 065 066/** 067 * @see IHapiTransactionService for an explanation of this class 068 */ 069public class HapiTransactionService implements IHapiTransactionService { 070 071 public static final String XACT_USERDATA_KEY_RESOLVED_TAG_DEFINITIONS = 072 HapiTransactionService.class.getName() + "_RESOLVED_TAG_DEFINITIONS"; 073 public static final String XACT_USERDATA_KEY_EXISTING_SEARCH_PARAMS = 074 HapiTransactionService.class.getName() + "_EXISTING_SEARCH_PARAMS"; 075 private static final Logger ourLog = LoggerFactory.getLogger(HapiTransactionService.class); 076 private static final ThreadLocal<RequestPartitionId> ourRequestPartitionThreadLocal = new ThreadLocal<>(); 077 private static final ThreadLocal<HapiTransactionService> ourExistingTransaction = new ThreadLocal<>(); 078 079 /** 080 * Default value for {@link #setTransactionPropagationWhenChangingPartitions(Propagation)} 081 * 082 * @since 7.6.0 083 */ 084 public static final Propagation DEFAULT_TRANSACTION_PROPAGATION_WHEN_CHANGING_PARTITIONS = Propagation.REQUIRED; 085 086 @Autowired 087 protected IInterceptorBroadcaster myInterceptorBroadcaster; 088 089 @Autowired 090 protected PlatformTransactionManager myTransactionManager; 091 092 @Autowired 093 protected IRequestPartitionHelperSvc myRequestPartitionHelperSvc; 094 095 @Autowired 096 protected PartitionSettings myPartitionSettings; 097 098 private Propagation myTransactionPropagationWhenChangingPartitions = 099 DEFAULT_TRANSACTION_PROPAGATION_WHEN_CHANGING_PARTITIONS; 100 101 private SleepUtil mySleepUtil = new SleepUtil(); 102 103 @VisibleForTesting 104 public void setInterceptorBroadcaster(IInterceptorBroadcaster theInterceptorBroadcaster) { 105 myInterceptorBroadcaster = theInterceptorBroadcaster; 106 } 107 108 @VisibleForTesting 109 public void setSleepUtil(SleepUtil theSleepUtil) { 110 mySleepUtil = theSleepUtil; 111 } 112 113 @Override 114 public IExecutionBuilder withRequest(@Nullable RequestDetails theRequestDetails) { 115 return buildExecutionBuilder(theRequestDetails); 116 } 117 118 @Override 119 public IExecutionBuilder withSystemRequest() { 120 return buildExecutionBuilder(null); 121 } 122 123 protected IExecutionBuilder buildExecutionBuilder(@Nullable RequestDetails theRequestDetails) { 124 return new ExecutionBuilder(theRequestDetails); 125 } 126 127 /** 128 * @deprecated Use {@link #withRequest(RequestDetails)} with fluent call instead 129 */ 130 @Deprecated 131 public <T> T execute( 132 @Nullable RequestDetails theRequestDetails, 133 @Nullable TransactionDetails theTransactionDetails, 134 @Nonnull TransactionCallback<T> theCallback) { 135 return execute(theRequestDetails, theTransactionDetails, theCallback, null); 136 } 137 138 /** 139 * @deprecated Use {@link #withRequest(RequestDetails)} with fluent call instead 140 */ 141 @Deprecated 142 public void execute( 143 @Nullable RequestDetails theRequestDetails, 144 @Nullable TransactionDetails theTransactionDetails, 145 @Nonnull Propagation thePropagation, 146 @Nonnull Isolation theIsolation, 147 @Nonnull Runnable theCallback) { 148 TransactionCallbackWithoutResult callback = new TransactionCallbackWithoutResult() { 149 @Override 150 protected void doInTransactionWithoutResult(TransactionStatus status) { 151 theCallback.run(); 152 } 153 }; 154 execute(theRequestDetails, theTransactionDetails, callback, null, thePropagation, theIsolation); 155 } 156 157 /** 158 * @deprecated Use {@link #withRequest(RequestDetails)} with fluent call instead 159 */ 160 @Deprecated 161 @Override 162 public <T> T withRequest( 163 @Nullable RequestDetails theRequestDetails, 164 @Nullable TransactionDetails theTransactionDetails, 165 @Nonnull Propagation thePropagation, 166 @Nonnull Isolation theIsolation, 167 @Nonnull ICallable<T> theCallback) { 168 169 TransactionCallback<T> callback = tx -> theCallback.call(); 170 return execute(theRequestDetails, theTransactionDetails, callback, null, thePropagation, theIsolation); 171 } 172 173 /** 174 * @deprecated Use {@link #withRequest(RequestDetails)} with fluent call instead 175 */ 176 @Deprecated 177 public <T> T execute( 178 @Nullable RequestDetails theRequestDetails, 179 @Nullable TransactionDetails theTransactionDetails, 180 @Nonnull TransactionCallback<T> theCallback, 181 @Nullable Runnable theOnRollback) { 182 return execute(theRequestDetails, theTransactionDetails, theCallback, theOnRollback, null, null); 183 } 184 185 @SuppressWarnings("ConstantConditions") 186 /** 187 * @deprecated Use {@link #withRequest(RequestDetails)} with fluent call instead 188 */ 189 @Deprecated 190 public <T> T execute( 191 @Nullable RequestDetails theRequestDetails, 192 @Nullable TransactionDetails theTransactionDetails, 193 @Nonnull TransactionCallback<T> theCallback, 194 @Nullable Runnable theOnRollback, 195 @Nullable Propagation thePropagation, 196 @Nullable Isolation theIsolation) { 197 return withRequest(theRequestDetails) 198 .withTransactionDetails(theTransactionDetails) 199 .withPropagation(thePropagation) 200 .withIsolation(theIsolation) 201 .onRollback(theOnRollback) 202 .execute(theCallback); 203 } 204 205 /** 206 * @deprecated Use {@link #withRequest(RequestDetails)} with fluent call instead 207 */ 208 @Deprecated 209 public <T> T execute( 210 @Nullable RequestDetails theRequestDetails, 211 @Nullable TransactionDetails theTransactionDetails, 212 @Nonnull TransactionCallback<T> theCallback, 213 @Nullable Runnable theOnRollback, 214 @Nonnull Propagation thePropagation, 215 @Nonnull Isolation theIsolation, 216 RequestPartitionId theRequestPartitionId) { 217 return withRequest(theRequestDetails) 218 .withTransactionDetails(theTransactionDetails) 219 .withPropagation(thePropagation) 220 .withIsolation(theIsolation) 221 .withRequestPartitionId(theRequestPartitionId) 222 .onRollback(theOnRollback) 223 .execute(theCallback); 224 } 225 226 public boolean isCustomIsolationSupported() { 227 return false; 228 } 229 230 @VisibleForTesting 231 public void setRequestPartitionSvcForUnitTest(IRequestPartitionHelperSvc theRequestPartitionHelperSvc) { 232 myRequestPartitionHelperSvc = theRequestPartitionHelperSvc; 233 } 234 235 public PlatformTransactionManager getTransactionManager() { 236 return myTransactionManager; 237 } 238 239 @VisibleForTesting 240 public void setTransactionManager(PlatformTransactionManager theTransactionManager) { 241 myTransactionManager = theTransactionManager; 242 } 243 244 @VisibleForTesting 245 public void setPartitionSettingsForUnitTest(PartitionSettings thePartitionSettings) { 246 myPartitionSettings = thePartitionSettings; 247 } 248 249 @Nullable 250 protected <T> T doExecute(ExecutionBuilder theExecutionBuilder, TransactionCallback<T> theCallback) { 251 final RequestPartitionId requestPartitionId = theExecutionBuilder.getEffectiveRequestPartitionId(); 252 RequestPartitionId previousRequestPartitionId = null; 253 if (requestPartitionId != null) { 254 previousRequestPartitionId = ourRequestPartitionThreadLocal.get(); 255 ourRequestPartitionThreadLocal.set(requestPartitionId); 256 } 257 258 ourLog.trace("Starting doExecute for RequestPartitionId {}", requestPartitionId); 259 if (isCompatiblePartition(previousRequestPartitionId, requestPartitionId)) { 260 if (ourExistingTransaction.get() == this && canReuseExistingTransaction(theExecutionBuilder)) { 261 /* 262 * If we're already in an active transaction, and it's for the right partition, 263 * and it's not a read-only transaction, we don't need to open a new transaction 264 * so let's just add a method to the stack trace that makes this obvious. 265 */ 266 return executeInExistingTransaction(theCallback); 267 } 268 } 269 270 HapiTransactionService previousExistingTransaction = ourExistingTransaction.get(); 271 try { 272 ourExistingTransaction.set(this); 273 274 if (isRequiresNewTransactionWhenChangingPartitions()) { 275 return executeInNewTransactionForPartitionChange( 276 theExecutionBuilder, theCallback, requestPartitionId, previousRequestPartitionId); 277 } else { 278 return doExecuteInTransaction( 279 theExecutionBuilder, theCallback, requestPartitionId, previousRequestPartitionId); 280 } 281 } finally { 282 ourExistingTransaction.set(previousExistingTransaction); 283 } 284 } 285 286 protected boolean isRequiresNewTransactionWhenChangingPartitions() { 287 return myTransactionPropagationWhenChangingPartitions == Propagation.REQUIRES_NEW; 288 } 289 290 @Override 291 public boolean isCompatiblePartition( 292 RequestPartitionId theRequestPartitionId, RequestPartitionId theOtherRequestPartitionId) { 293 return !myPartitionSettings.isPartitioningEnabled() 294 || !isRequiresNewTransactionWhenChangingPartitions() 295 || Objects.equals(theRequestPartitionId, theOtherRequestPartitionId); 296 } 297 298 @Nullable 299 private <T> T executeInNewTransactionForPartitionChange( 300 ExecutionBuilder theExecutionBuilder, 301 TransactionCallback<T> theCallback, 302 RequestPartitionId requestPartitionId, 303 RequestPartitionId previousRequestPartitionId) { 304 ourLog.trace("executeInNewTransactionForPartitionChange"); 305 theExecutionBuilder.myPropagation = myTransactionPropagationWhenChangingPartitions; 306 return doExecuteInTransaction(theExecutionBuilder, theCallback, requestPartitionId, previousRequestPartitionId); 307 } 308 309 private boolean isThrowableOrItsSubclassPresent(Throwable theThrowable, Class<? extends Throwable> theClass) { 310 return ExceptionUtils.indexOfType(theThrowable, theClass) != -1; 311 } 312 313 private boolean isThrowablePresent(Throwable theThrowable, Class<? extends Throwable> theClass) { 314 return ExceptionUtils.indexOfThrowable(theThrowable, theClass) != -1; 315 } 316 317 private boolean isRetriable(Throwable theThrowable) { 318 return isThrowablePresent(theThrowable, ResourceVersionConflictException.class) 319 || isThrowablePresent(theThrowable, DataIntegrityViolationException.class) 320 || isThrowablePresent(theThrowable, ConstraintViolationException.class) 321 || isThrowablePresent(theThrowable, ObjectOptimisticLockingFailureException.class) 322 // calling isThrowableOrItsSubclassPresent instead of isThrowablePresent for 323 // PessimisticLockingFailureException, because we want to retry on its subclasses as well, especially 324 // CannotAcquireLockException, which is thrown in some deadlock situations which we want to retry 325 || isThrowableOrItsSubclassPresent(theThrowable, PessimisticLockingFailureException.class); 326 } 327 328 @Nullable 329 private <T> T doExecuteInTransaction( 330 ExecutionBuilder theExecutionBuilder, 331 TransactionCallback<T> theCallback, 332 RequestPartitionId requestPartitionId, 333 RequestPartitionId previousRequestPartitionId) { 334 ourLog.trace("doExecuteInTransaction"); 335 try { 336 for (int i = 0; ; i++) { 337 try { 338 339 return doExecuteCallback(theExecutionBuilder, theCallback); 340 341 } catch (Exception e) { 342 if (!isRetriable(e)) { 343 ourLog.debug("Unexpected transaction exception. Will not be retried.", e); 344 throw e; 345 } else { 346 347 ourLog.debug("Version conflict detected", e); 348 349 if (theExecutionBuilder.myOnRollback != null) { 350 theExecutionBuilder.myOnRollback.run(); 351 } 352 353 int maxRetries = 0; 354 355 /* 356 * If two client threads both concurrently try to add the same tag that isn't 357 * known to the system already, they'll both try to create a row in HFJ_TAG_DEF, 358 * which is the tag definition table. In that case, a constraint error will be 359 * thrown by one of the client threads, so we auto-retry in order to avoid 360 * annoying spurious failures for the client. 361 */ 362 if (DaoFailureUtil.isTagStorageFailure(e)) { 363 maxRetries = 3; 364 } 365 366 if (maxRetries == 0) { 367 IInterceptorBroadcaster compositeBroadcaster = 368 CompositeInterceptorBroadcaster.newCompositeBroadcaster( 369 myInterceptorBroadcaster, theExecutionBuilder.myRequestDetails); 370 if (compositeBroadcaster.hasHooks(Pointcut.STORAGE_VERSION_CONFLICT)) { 371 HookParams params = new HookParams() 372 .add(RequestDetails.class, theExecutionBuilder.myRequestDetails) 373 .addIfMatchesType( 374 ServletRequestDetails.class, theExecutionBuilder.myRequestDetails); 375 ResourceVersionConflictResolutionStrategy conflictResolutionStrategy = 376 (ResourceVersionConflictResolutionStrategy) 377 compositeBroadcaster.callHooksAndReturnObject( 378 Pointcut.STORAGE_VERSION_CONFLICT, params); 379 if (conflictResolutionStrategy != null && conflictResolutionStrategy.isRetry()) { 380 maxRetries = conflictResolutionStrategy.getMaxRetries(); 381 } 382 } 383 } 384 385 if (i < maxRetries) { 386 if (theExecutionBuilder.myTransactionDetails != null) { 387 theExecutionBuilder 388 .myTransactionDetails 389 .getRollbackUndoActions() 390 .forEach(Runnable::run); 391 theExecutionBuilder.myTransactionDetails.clearRollbackUndoActions(); 392 theExecutionBuilder.myTransactionDetails.clearResolvedItems(); 393 theExecutionBuilder.myTransactionDetails.clearUserData( 394 XACT_USERDATA_KEY_RESOLVED_TAG_DEFINITIONS); 395 theExecutionBuilder.myTransactionDetails.clearUserData( 396 XACT_USERDATA_KEY_EXISTING_SEARCH_PARAMS); 397 } 398 double sleepAmount = (250.0d * i) * Math.random(); 399 long sleepAmountLong = (long) sleepAmount; 400 mySleepUtil.sleepAtLeast(sleepAmountLong, false); 401 402 ourLog.info( 403 "About to start a transaction retry due to conflict or constraint error. Sleeping {}ms first.", 404 sleepAmountLong); 405 continue; 406 } 407 408 IBaseOperationOutcome oo = null; 409 if (e instanceof ResourceVersionConflictException) { 410 oo = ((ResourceVersionConflictException) e).getOperationOutcome(); 411 } 412 413 if (maxRetries > 0) { 414 String msg = 415 "Max retries (" + maxRetries + ") exceeded for version conflict: " + e.getMessage(); 416 ourLog.info(msg, maxRetries); 417 throw new ResourceVersionConflictException(Msg.code(549) + msg); 418 } 419 420 throw new ResourceVersionConflictException(Msg.code(550) + e.getMessage(), e, oo); 421 } 422 } 423 } 424 } finally { 425 if (requestPartitionId != null) { 426 ourRequestPartitionThreadLocal.set(previousRequestPartitionId); 427 } 428 } 429 } 430 431 public void setTransactionPropagationWhenChangingPartitions( 432 Propagation theTransactionPropagationWhenChangingPartitions) { 433 Validate.notNull(theTransactionPropagationWhenChangingPartitions); 434 myTransactionPropagationWhenChangingPartitions = theTransactionPropagationWhenChangingPartitions; 435 } 436 437 @Nullable 438 protected <T> T doExecuteCallback(ExecutionBuilder theExecutionBuilder, TransactionCallback<T> theCallback) { 439 try { 440 TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); 441 442 if (theExecutionBuilder.myPropagation != null) { 443 txTemplate.setPropagationBehavior(theExecutionBuilder.myPropagation.value()); 444 } 445 446 if (isCustomIsolationSupported() 447 && theExecutionBuilder.myIsolation != null 448 && theExecutionBuilder.myIsolation != Isolation.DEFAULT) { 449 txTemplate.setIsolationLevel(theExecutionBuilder.myIsolation.value()); 450 } 451 452 if (theExecutionBuilder.myReadOnly) { 453 txTemplate.setReadOnly(true); 454 } 455 456 return txTemplate.execute(theCallback); 457 } catch (MyException e) { 458 if (e.getCause() instanceof RuntimeException) { 459 throw (RuntimeException) e.getCause(); 460 } else { 461 throw new InternalErrorException(Msg.code(551) + e); 462 } 463 } 464 } 465 466 protected class ExecutionBuilder implements IExecutionBuilder, TransactionOperations, Cloneable { 467 468 private final RequestDetails myRequestDetails; 469 private Isolation myIsolation; 470 private Propagation myPropagation; 471 private boolean myReadOnly; 472 private TransactionDetails myTransactionDetails; 473 private Runnable myOnRollback; 474 protected RequestPartitionId myRequestPartitionId; 475 476 protected ExecutionBuilder(RequestDetails theRequestDetails) { 477 myRequestDetails = theRequestDetails; 478 } 479 480 @Override 481 public ExecutionBuilder withIsolation(Isolation theIsolation) { 482 assert myIsolation == null; 483 myIsolation = theIsolation; 484 return this; 485 } 486 487 @Override 488 public ExecutionBuilder withTransactionDetails(TransactionDetails theTransactionDetails) { 489 assert myTransactionDetails == null; 490 myTransactionDetails = theTransactionDetails; 491 return this; 492 } 493 494 @Override 495 public ExecutionBuilder withPropagation(Propagation thePropagation) { 496 assert myPropagation == null; 497 myPropagation = thePropagation; 498 return this; 499 } 500 501 @Override 502 public ExecutionBuilder withRequestPartitionId(RequestPartitionId theRequestPartitionId) { 503 assert myRequestPartitionId == null; 504 myRequestPartitionId = theRequestPartitionId; 505 return this; 506 } 507 508 @Override 509 public ExecutionBuilder readOnly() { 510 myReadOnly = true; 511 return this; 512 } 513 514 @Override 515 public ExecutionBuilder onRollback(Runnable theOnRollback) { 516 assert myOnRollback == null; 517 myOnRollback = theOnRollback; 518 return this; 519 } 520 521 @Override 522 public void execute(Runnable theTask) { 523 TransactionCallback<Void> task = tx -> { 524 theTask.run(); 525 return null; 526 }; 527 execute(task); 528 } 529 530 @Override 531 public <T> T execute(Callable<T> theTask) { 532 TransactionCallback<T> callback = tx -> invokeCallableAndHandleAnyException(theTask); 533 return execute(callback); 534 } 535 536 @Override 537 public <T> T execute(@Nonnull TransactionCallback<T> callback) { 538 assert callback != null; 539 540 return doExecute(this, callback); 541 } 542 543 @VisibleForTesting 544 public RequestPartitionId getRequestPartitionIdForTesting() { 545 return myRequestPartitionId; 546 } 547 548 @VisibleForTesting 549 public RequestDetails getRequestDetailsForTesting() { 550 return myRequestDetails; 551 } 552 553 public Propagation getPropagation() { 554 return myPropagation; 555 } 556 557 @Nullable 558 protected RequestPartitionId getEffectiveRequestPartitionId() { 559 final RequestPartitionId requestPartitionId; 560 if (myRequestPartitionId != null) { 561 requestPartitionId = myRequestPartitionId; 562 } else if (myRequestDetails != null) { 563 requestPartitionId = myRequestPartitionHelperSvc.determineGenericPartitionForRequest(myRequestDetails); 564 } else { 565 requestPartitionId = null; 566 } 567 return requestPartitionId; 568 } 569 } 570 571 /** 572 * This is just an unchecked exception so that we can catch checked exceptions inside TransactionTemplate 573 * and rethrow them outside of it 574 */ 575 static class MyException extends RuntimeException { 576 577 public MyException(Throwable theThrowable) { 578 super(theThrowable); 579 } 580 } 581 582 /** 583 * Returns true if we already have an active transaction associated with the current thread, AND 584 * either it's non-read-only or we only need a read-only transaction, AND 585 * the newly requested transaction has a propagation of REQUIRED 586 */ 587 private static boolean canReuseExistingTransaction(ExecutionBuilder theExecutionBuilder) { 588 return TransactionSynchronizationManager.isActualTransactionActive() 589 && (!TransactionSynchronizationManager.isCurrentTransactionReadOnly() || theExecutionBuilder.myReadOnly) 590 && (theExecutionBuilder.myPropagation == null 591 || theExecutionBuilder.myPropagation 592 == DEFAULT_TRANSACTION_PROPAGATION_WHEN_CHANGING_PARTITIONS); 593 } 594 595 @Nullable 596 private static <T> T executeInExistingTransaction(@Nonnull TransactionCallback<T> theCallback) { 597 ourLog.trace("executeInExistingTransaction"); 598 // TODO we could probably track the TransactionStatus we need as a thread local like we do our partition id. 599 return theCallback.doInTransaction(null); 600 } 601 602 /** 603 * Invokes {@link Callable#call()} and rethrows any exceptions thrown by that method. 604 * If the exception extends {@link BaseServerResponseException} it is rethrown unmodified. 605 * Otherwise, it's wrapped in a {@link InternalErrorException}. 606 */ 607 public static <T> T invokeCallableAndHandleAnyException(Callable<T> theTask) { 608 try { 609 return theTask.call(); 610 } catch (BaseServerResponseException e) { 611 throw e; 612 } catch (Exception e) { 613 throw new InternalErrorException(Msg.code(2223) + e.getMessage(), e); 614 } 615 } 616 617 public static <T> T executeWithDefaultPartitionInContext(@Nonnull ICallable<T> theCallback) { 618 RequestPartitionId previousRequestPartitionId = ourRequestPartitionThreadLocal.get(); 619 ourRequestPartitionThreadLocal.set(RequestPartitionId.defaultPartition()); 620 try { 621 return theCallback.call(); 622 } finally { 623 ourRequestPartitionThreadLocal.set(previousRequestPartitionId); 624 } 625 } 626 627 public static RequestPartitionId getRequestPartitionAssociatedWithThread() { 628 return ourRequestPartitionThreadLocal.get(); 629 } 630 631 /** 632 * Throws an {@link IllegalArgumentException} if a transaction is active 633 */ 634 public static void noTransactionAllowed() { 635 Validate.isTrue( 636 !TransactionSynchronizationManager.isActualTransactionActive(), 637 "Transaction must not be active but found an active transaction"); 638 } 639 640 /** 641 * Throws an {@link IllegalArgumentException} if no transaction is active 642 */ 643 public static void requireTransaction() { 644 Validate.isTrue( 645 TransactionSynchronizationManager.isActualTransactionActive(), 646 "Transaction required here but no active transaction found"); 647 } 648}