├── .github └── workflows │ └── maven.yml ├── .gitignore ├── .travis.yml ├── README.md ├── license.txt ├── pom.xml ├── settings.xml └── src ├── main ├── java │ ├── module-info.java │ └── org │ │ └── omnifaces │ │ └── services │ │ ├── CdiExtension.java │ │ ├── Service.java │ │ ├── asynchronous │ │ ├── Asynchronous.java │ │ ├── AsynchronousInterceptor.java │ │ ├── ExecutorBean.java │ │ └── FutureDelegator.java │ │ ├── lock │ │ ├── Lock.java │ │ └── LockInterceptor.java │ │ ├── pooled │ │ ├── PoolKey.java │ │ ├── PoolLockTimeoutException.java │ │ ├── Pooled.java │ │ ├── PooledContext.java │ │ ├── PooledScope.java │ │ ├── PooledScopeEnabled.java │ │ ├── PooledScopeInterceptor.java │ │ └── UncheckedInterruptedException.java │ │ └── util │ │ ├── AnnotatedMethodWrapper.java │ │ ├── AnnotatedTypeWrapper.java │ │ └── CdiUtils.java └── resources │ └── META-INF │ └── services │ └── jakarta.enterprise.inject.spi.Extension └── test ├── java └── org │ └── omnifaces │ └── test │ └── services │ └── pooled │ ├── PooledTest.java │ ├── SingleInstancePooledBean.java │ └── testing │ └── DependencyInjectionArquillianExtension.java └── resources ├── arquillian.xml └── server.xml /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2021 OmniFaces 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | # the License. You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | # specific language governing permissions and limitations under the License. 12 | # 13 | 14 | name: Java CI with Maven 15 | 16 | on: [push, pull_request] 17 | 18 | jobs: 19 | test: 20 | name: Run tests against ${{ matrix.server }} on JDK ${{ matrix.jdk }} 21 | runs-on: ubuntu-latest 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | jdk: [11, 17] 26 | server: [wildfly, glassfish6, liberty] 27 | # Temporarily disabled: payara5, tomee9 28 | 29 | steps: 30 | - uses: actions/checkout@v2 31 | - name: Set up JDK ${{ matrix.jdk }} 32 | uses: actions/setup-java@v1 33 | with: 34 | java-version: ${{ matrix.jdk }} 35 | - name: Test with Maven 36 | run: mvn verify -Dmaven.javadoc.skip=true -P ${{ matrix.server }} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/java,maven,eclipse,intellij,netbeans,windows,osx,linux 3 | 4 | ### Java ### 5 | *.class 6 | 7 | # Mobile Tools for Java (J2ME) 8 | .mtj.tmp/ 9 | 10 | # Package Files # 11 | *.jar 12 | *.war 13 | *.ear 14 | 15 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 16 | hs_err_pid* 17 | 18 | 19 | ### Maven ### 20 | target/ 21 | pom.xml.tag 22 | pom.xml.releaseBackup 23 | pom.xml.versionsBackup 24 | pom.xml.next 25 | release.properties 26 | dependency-reduced-pom.xml 27 | buildNumber.properties 28 | .mvn/timing.properties 29 | 30 | 31 | ### Eclipse ### 32 | 33 | .metadata 34 | bin/ 35 | tmp/ 36 | *.tmp 37 | *.bak 38 | *.swp 39 | *~.nib 40 | local.properties 41 | .settings/ 42 | .loadpath 43 | 44 | # Eclipse Core 45 | .project 46 | 47 | # External tool builders 48 | .externalToolBuilders/ 49 | 50 | # Locally stored "Eclipse launch configurations" 51 | *.launch 52 | 53 | # PyDev specific (Python IDE for Eclipse) 54 | *.pydevproject 55 | 56 | # CDT-specific (C/C++ Development Tooling) 57 | .cproject 58 | 59 | # JDT-specific (Eclipse Java Development Tools) 60 | .classpath 61 | 62 | # Java annotation processor (APT) 63 | .factorypath 64 | 65 | # PDT-specific (PHP Development Tools) 66 | .buildpath 67 | 68 | # sbteclipse plugin 69 | .target 70 | 71 | # Tern plugin 72 | .tern-project 73 | 74 | # TeXlipse plugin 75 | .texlipse 76 | 77 | # STS (Spring Tool Suite) 78 | .springBeans 79 | 80 | # Code Recommenders 81 | .recommenders/ 82 | 83 | 84 | ### Intellij ### 85 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 86 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 87 | 88 | # User-specific stuff: 89 | .idea/workspace.xml 90 | .idea/tasks.xml 91 | .idea/dictionaries 92 | .idea/vcs.xml 93 | .idea/jsLibraryMappings.xml 94 | 95 | # Sensitive or high-churn files: 96 | .idea/dataSources.ids 97 | .idea/dataSources.xml 98 | .idea/sqlDataSources.xml 99 | .idea/dynamic.xml 100 | .idea/uiDesigner.xml 101 | 102 | # Gradle: 103 | .idea/gradle.xml 104 | .idea/libraries 105 | 106 | # Mongo Explorer plugin: 107 | .idea/mongoSettings.xml 108 | 109 | ## File-based project format: 110 | *.iws 111 | 112 | ## Plugin-specific files: 113 | 114 | # IntelliJ 115 | /out/ 116 | 117 | # mpeltonen/sbt-idea plugin 118 | .idea_modules/ 119 | 120 | # JIRA plugin 121 | atlassian-ide-plugin.xml 122 | 123 | # Crashlytics plugin (for Android Studio and IntelliJ) 124 | com_crashlytics_export_strings.xml 125 | crashlytics.properties 126 | crashlytics-build.properties 127 | fabric.properties 128 | 129 | # Manually added 130 | .idea/ 131 | *.iml 132 | 133 | ### NetBeans ### 134 | nbproject/private/ 135 | build/ 136 | nbbuild/ 137 | dist/ 138 | nbdist/ 139 | nbactions.xml 140 | .nb-gradle/ 141 | 142 | 143 | ### Windows ### 144 | # Windows image file caches 145 | Thumbs.db 146 | ehthumbs.db 147 | 148 | # Folder config file 149 | Desktop.ini 150 | 151 | # Recycle Bin used on file shares 152 | $RECYCLE.BIN/ 153 | 154 | # Windows Installer files 155 | *.cab 156 | *.msi 157 | *.msm 158 | *.msp 159 | 160 | # Windows shortcuts 161 | *.lnk 162 | 163 | 164 | ### OSX ### 165 | .DS_Store 166 | .AppleDouble 167 | .LSOverride 168 | 169 | # Icon must end with two \r 170 | Icon 171 | 172 | 173 | # Thumbnails 174 | ._* 175 | 176 | # Files that might appear in the root of a volume 177 | .DocumentRevisions-V100 178 | .fseventsd 179 | .Spotlight-V100 180 | .TemporaryItems 181 | .Trashes 182 | .VolumeIcon.icns 183 | 184 | # Directories potentially created on remote AFP share 185 | .AppleDB 186 | .AppleDesktop 187 | Network Trash Folder 188 | Temporary Items 189 | .apdisk 190 | 191 | 192 | ### Linux ### 193 | *~ 194 | 195 | # temporary files which can be created if a process still has a handle open of a deleted file 196 | .fuse_hidden* 197 | 198 | # KDE directory preferences 199 | .directory 200 | 201 | # Linux trash folder which might appear on any partition or disk 202 | .Trash-* 203 | 204 | 205 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | cache: 5 | directories: 6 | - "$HOME/.m2/repository" 7 | - "$HOME/.sonar/cache" 8 | 9 | # Do integration test on configured servers. 10 | env: 11 | - SERVER=payara 12 | - SERVER=wildfly 13 | - SERVER=glassfish 14 | - SERVER=tomee 15 | - SERVER=liberty 16 | script: mvn verify -Dmaven.javadoc.skip=true -P $SERVER 17 | 18 | # Do snapshot deployment. 19 | deploy: 20 | provider: script 21 | script: mvn deploy -Dmaven.test.skip -s settings.xml 22 | skip_cleanup: true 23 | on: 24 | branch: develop 25 | condition: $SERVER = wildfly 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OmniServices 2 | 3 | [![Build Status](https://travis-ci.org/omnifaces/omniservices.svg?branch=develop)](https://travis-ci.org/omnifaces/omniservices) 4 | [![Maven](https://maven-badges.herokuapp.com/maven-central/org.omnifaces/omniservices/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.omnifaces/omniservices) 5 | [![Javadoc](http://javadoc.io/badge/org.omnifaces/omniservices.svg)](http://javadoc.io/doc/org.omnifaces/omniservices) 6 | [![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) 7 | 8 | Utility library that provides EJB3-like features for CDI beans 9 | 10 | This project will attempt to implement CDI and Interceptor based versions of various EJB features and services. Currently the following features are implemented: 11 | 12 | * EJB @Asynchronous - CDI based [@Asynchronous](https://www.javadoc.io/static/org.omnifaces/omniservices/0.4/org.omnifaces.services/org/omnifaces/services/asynchronous/Asynchronous.html) (note: this is currently available in native Jakarta EE via Jakarta Concurrency 3.0) 13 | * EJB @Stateless - CDI based [@Pooled](https://github.com/omnifaces/omniservices/blob/develop/src/main/java/org/omnifaces/services/pooled/Pooled.java)+@Transactional or [@Service](https://github.com/omnifaces/omniservices/blob/develop/src/main/java/org/omnifaces/services/Service.java) 14 | * EJB @Lock - CDI based [@Lock](https://github.com/omnifaces/omniservices/blob/develop/src/main/java/org/omnifaces/services/lock/Lock.java) 15 | 16 | The following features were being considered for future versions: 17 | 18 | * EJB @Schedule - this is currently available in native Jakarta EE 11 via Jakarta Concurrency 3.1 19 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2021 OmniFaces 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 4 | the License. You may obtain a copy of the License at 5 | 6 | https://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 9 | an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 10 | specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 4.0.0 8 | 9 | org.omnifaces 10 | omniservices 11 | 0.5-SNAPSHOT 12 | jar 13 | 14 | OmniServices 15 | Utility library that provides EJB3-like features for CDI beans 16 | https://github.com/omnifaces/omniservices 17 | 18 | OmniFaces 19 | https://omnifaces.org 20 | 21 | 2016 22 | 23 | 24 | 25 | balusc 26 | Bauke Scholtz 27 | balusc@gmail.com 28 | 29 | 30 | arjan.tijms 31 | Arjan Tijms 32 | arjan.tijms@gmail.com 33 | 34 | 35 | jan.beernink 36 | Jan Beernink 37 | jan.beernink@gmail.com 38 | 39 | 40 | 41 | 42 | 43 | The Apache Software License, Version 2.0 44 | https://www.apache.org/licenses/LICENSE-2.0.txt 45 | repo 46 | 47 | 48 | 49 | 50 | https://github.com/omnifaces/omniservices 51 | scm:git:git://github.com/omnifaces/omniservices.git 52 | scm:git:git@github.com:omnifaces/omniservices.git 53 | 54 | 55 | 56 | 57 | ossrh 58 | https://oss.sonatype.org/content/repositories/snapshots 59 | 60 | 61 | 62 | 63 | 11 64 | 65 | 66 | UTF-8 67 | UTF-8 68 | 69 | 70 | 0.8.7 71 | 72 | 22.10.0 73 | 6.2.5 74 | 5.2021.9 75 | 25.0.1.Final 76 | 9.0.0-M7 77 | 21.0.0.12 78 | 3.0.0-M5 79 | 80 | 81 | 82 | 83 | 84 | jakarta.enterprise 85 | jakarta.enterprise.cdi-api 86 | 4.0.1 87 | provided 88 | 89 | 90 | 91 | jakarta.ejb 92 | jakarta.ejb-api 93 | 4.0.1 94 | provided 95 | 96 | 97 | 98 | 99 | 100 | org.omnifaces 101 | omniutils 102 | 0.13 103 | 104 | 105 | 106 | 107 | org.junit.jupiter 108 | junit-jupiter-api 109 | test 110 | 111 | 112 | 113 | org.junit.jupiter 114 | junit-jupiter-engine 115 | test 116 | 117 | 118 | 119 | org.jboss.arquillian.junit5 120 | arquillian-junit5-container 121 | test 122 | 123 | 124 | 125 | org.jboss.arquillian.protocol 126 | arquillian-protocol-servlet-jakarta 127 | test 128 | 129 | 130 | 131 | org.jboss.shrinkwrap.resolver 132 | shrinkwrap-resolver-depchain 133 | pom 134 | test 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | org.jboss.arquillian 156 | arquillian-bom 157 | 1.7.0.Alpha13 158 | pom 159 | import 160 | 161 | 162 | 163 | org.junit 164 | junit-bom 165 | 5.8.1 166 | pom 167 | import 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | com.mycila 178 | license-maven-plugin 179 | 3.0 180 | 181 |
license.txt
182 | 183 | *.* 184 | 185 | 186 | SLASHSTAR_STYLE 187 | 188 |
189 | 190 | 191 | process-sources 192 | 193 | format 194 | 195 | 196 | 197 |
198 | 199 | 200 | 201 | org.apache.maven.plugins 202 | maven-jar-plugin 203 | 3.2.0 204 | 205 | 206 | 207 | true 208 | true 209 | 210 | 211 | ${project.url} 212 | ${project.artifactId} 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | org.apache.maven.plugins 221 | maven-source-plugin 222 | 3.2.1 223 | 224 | 225 | attach-sources 226 | 227 | jar-no-fork 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | org.apache.maven.plugins 236 | maven-javadoc-plugin 237 | 3.2.0 238 | 239 | true 240 | true 241 | true 242 | OmniServices API documentation 243 | 244 | https://javaee.github.io/javaee-spec/javadocs/ 245 | 246 | 247 | 248 | 249 | attach-javadocs 250 | 251 | jar 252 | 253 | 254 | 255 | 256 | 257 | 258 | external.atlassian.jgitflow 259 | jgitflow-maven-plugin 260 | 1.0-m5.1 261 | 262 | true 263 | true 264 | 265 | 266 | 267 | 268 | org.jacoco 269 | jacoco-maven-plugin 270 | ${jacoco.version} 271 | 272 | 273 | 274 | prepare-agent 275 | report 276 | 277 | 278 | 279 | 280 | 281 | 282 | org.apache.maven.plugins 283 | maven-compiler-plugin 284 | 3.8.1 285 | 286 | 287 | 288 | org.apache.maven.plugins 289 | maven-surefire-plugin 290 | ${maven-surefire-plugin.version} 291 | 292 | 293 | 294 | 295 | org.sonatype.plugins 296 | nexus-staging-maven-plugin 297 | 1.6.13 298 | true 299 | 300 | ossrh 301 | https://oss.sonatype.org/ 302 | true 303 | 304 | 305 |
306 | 307 | 308 | 309 | 310 | 311 | 312 | org.eclipse.m2e 313 | lifecycle-mapping 314 | 1.0.0 315 | 316 | 317 | 318 | 319 | 320 | com.mycila 321 | license-maven-plugin 322 | [3.0,) 323 | 324 | format 325 | 326 | 327 | 328 | 329 | true 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 |
340 | 341 | 342 | 343 | piranha-micro 344 | 345 | true 346 | 347 | 348 | 349 | 350 | cloud.piranha.arquillian 351 | piranha-arquillian-server 352 | ${piranha.version} 353 | test 354 | 355 | 356 | 357 | 358 | 359 | glassfish6 360 | 361 | 362 | 363 | org.glassfish.main.extras 364 | glassfish-embedded-all 365 | ${glassfish6.version} 366 | test 367 | 368 | 369 | 370 | org.omnifaces.arquillian 371 | arquillian-glassfish-server-embedded 372 | 1.0 373 | test 374 | 375 | 376 | 377 | 378 | 379 | payara5 380 | 381 | 382 | 383 | fish.payara.extras 384 | payara-embedded-all 385 | ${payara5.version} 386 | test 387 | 388 | 389 | 390 | fish.payara.arquillian 391 | arquillian-payara-server-embedded 392 | 2.4.5 393 | 394 | 395 | 396 | 397 | 398 | wildfly 399 | 400 | 401 | 402 | org.wildfly.arquillian 403 | wildfly-arquillian-container-managed 404 | 4.0.0.Alpha3 405 | test 406 | 407 | 408 | 409 | 410 | 411 | 412 | org.apache.maven.plugins 413 | maven-dependency-plugin 414 | 415 | 416 | unpack 417 | process-test-classes 418 | 419 | unpack 420 | 421 | 422 | 423 | 424 | org.wildfly 425 | wildfly-preview-dist 426 | ${wildfly.version} 427 | zip 428 | false 429 | ${project.build.directory} 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | org.apache.maven.plugins 438 | maven-surefire-plugin 439 | ${maven-surefire-plugin.version} 440 | 441 | 442 | org.jboss.logmanager.LogManager 443 | 444 | 445 | ${project.build.directory}/wildfly-preview-${wildfly.version} 446 | ${project.build.directory}/wildfly-preview-${wildfly.version}/modules 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | tomee9 457 | 458 | 459 | 460 | org.apache.tomee 461 | apache-tomee 462 | ${tomee9.version} 463 | test 464 | zip 465 | plus 466 | 467 | 468 | 469 | org.apache.tomee 470 | arquillian-tomee-remote 471 | 8.0.7 472 | test 473 | 474 | 475 | org.apache.tomee 476 | arquillian-openejb-transaction-provider 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | org.apache.maven.plugins 486 | maven-surefire-plugin 487 | ${maven-surefire-plugin.version} 488 | 489 | -DTOMEE_LOCK_FILE=${user.dir}/.tomee-ports.lock 490 | 491 | -1 492 | -1 493 | -1 494 | -1 495 | 496 | target/tomee/server 497 | target/tomee/arquillian 498 | true 499 | plus 500 | 501 | openejb.environment.default=true 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | liberty 513 | 514 | 515 | 516 | io.openliberty.arquillian 517 | arquillian-liberty-managed-jakarta 518 | 2.0.1 519 | test 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | org.apache.maven.plugins 528 | maven-dependency-plugin 529 | 530 | 531 | unpack 532 | process-test-classes 533 | 534 | unpack 535 | 536 | 537 | 538 | 539 | io.openliberty 540 | openliberty-runtime 541 | ${liberty.version} 542 | zip 543 | false 544 | ${project.build.directory} 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | org.apache.maven.plugins 553 | maven-antrun-plugin 554 | 1.1 555 | 556 | 557 | process-test-classes 558 | 559 | run 560 | 561 | 562 | 563 | Copying server.xml 564 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | org.apache.maven.plugins 575 | maven-surefire-plugin 576 | ${maven-surefire-plugin.version} 577 | 578 | 579 | liberty 580 | ${project.build.directory}/wlp 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | release 592 | 593 | 594 | 595 | 596 | 597 | org.apache.maven.plugins 598 | maven-gpg-plugin 599 | 3.0.1 600 | 601 | 602 | sign-artifacts 603 | verify 604 | 605 | sign 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 |
616 | -------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ossrh 5 | ${env.SONATYPE_USERNAME} 6 | ${env.SONATYPE_PASSWORD} 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | import org.omnifaces.services.CdiExtension; 14 | 15 | import jakarta.enterprise.inject.spi.Extension; 16 | 17 | /* 18 | * Copyright 2021, 2023 OmniFaces 19 | * 20 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 21 | * the License. You may obtain a copy of the License at 22 | * 23 | * https://www.apache.org/licenses/LICENSE-2.0 24 | * 25 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 26 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 27 | * specific language governing permissions and limitations under the License. 28 | */ 29 | /** 30 | * @author Arjan Tijms 31 | */ 32 | module org.omnifaces.services { 33 | 34 | provides Extension with CdiExtension; 35 | 36 | exports org.omnifaces.services; 37 | opens org.omnifaces.services; 38 | 39 | exports org.omnifaces.services.asynchronous; 40 | opens org.omnifaces.services.asynchronous; 41 | 42 | exports org.omnifaces.services.pooled; 43 | opens org.omnifaces.services.pooled; 44 | 45 | exports org.omnifaces.services.lock; 46 | opens org.omnifaces.services.lock; 47 | 48 | exports org.omnifaces.services.util; 49 | opens org.omnifaces.services.util; 50 | 51 | requires transitive java.transaction.xa; 52 | 53 | requires java.naming; 54 | requires jakarta.cdi; 55 | requires jakarta.ejb; 56 | requires org.omnifaces.utils; 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/CdiExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services; 14 | 15 | import static org.omnifaces.utils.annotation.Annotations.createAnnotationInstance; 16 | 17 | import org.omnifaces.services.asynchronous.AsynchronousInterceptor; 18 | import org.omnifaces.services.asynchronous.ExecutorBean; 19 | import org.omnifaces.services.lock.Lock; 20 | import org.omnifaces.services.lock.LockInterceptor; 21 | import org.omnifaces.services.pooled.Pooled; 22 | import org.omnifaces.services.pooled.PooledContext; 23 | import org.omnifaces.services.pooled.PooledScopeEnabled; 24 | import org.omnifaces.services.util.AnnotatedTypeWrapper; 25 | 26 | import jakarta.ejb.Asynchronous; 27 | import jakarta.enterprise.event.Observes; 28 | import jakarta.enterprise.inject.spi.AfterBeanDiscovery; 29 | import jakarta.enterprise.inject.spi.AnnotatedType; 30 | import jakarta.enterprise.inject.spi.BeanManager; 31 | import jakarta.enterprise.inject.spi.BeforeBeanDiscovery; 32 | import jakarta.enterprise.inject.spi.Extension; 33 | import jakarta.enterprise.inject.spi.ProcessAnnotatedType; 34 | import jakarta.enterprise.inject.spi.ProcessBean; 35 | 36 | public class CdiExtension implements Extension { 37 | 38 | private final PooledContext pooledContext = new PooledContext(); 39 | 40 | public void beforeBeanDiscovery(@Observes BeforeBeanDiscovery beforeBeanDiscovery, BeanManager beanManager) { 41 | addAnnotatedTypes(beforeBeanDiscovery, beanManager, 42 | Asynchronous.class, 43 | AsynchronousInterceptor.class, 44 | 45 | Lock.class, 46 | LockInterceptor.class, 47 | 48 | ExecutorBean.class); 49 | 50 | beforeBeanDiscovery.addScope( 51 | Pooled.class, true, false); 52 | } 53 | 54 | public void processAnnotatedType(@Observes ProcessAnnotatedType processAnnotatedType) { 55 | if (processAnnotatedType.getAnnotatedType().isAnnotationPresent(Pooled.class)) { 56 | AnnotatedType annotatedType = processAnnotatedType.getAnnotatedType(); 57 | AnnotatedTypeWrapper wrapper = new AnnotatedTypeWrapper(annotatedType); 58 | 59 | wrapper.addAnnotation(createAnnotationInstance(PooledScopeEnabled.class)); 60 | 61 | processAnnotatedType.setAnnotatedType(wrapper); 62 | } 63 | 64 | } 65 | 66 | public void processBean(@Observes ProcessBean processBean) { 67 | Pooled pooled = processBean.getAnnotated().getAnnotation(Pooled.class); 68 | 69 | if (pooled != null) { 70 | pooledContext.createInstancePool(processBean.getBean(), pooled); 71 | } 72 | } 73 | 74 | public void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery) { 75 | afterBeanDiscovery.addContext(pooledContext); 76 | } 77 | 78 | public static void addAnnotatedTypes(BeforeBeanDiscovery beforeBean, BeanManager beanManager, Class... types) { 79 | for (Class type : types) { 80 | beforeBean.addAnnotatedType(beanManager.createAnnotatedType(type), "Omniservices " + type.getName()); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/Service.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services; 14 | 15 | import static java.lang.annotation.ElementType.TYPE; 16 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 17 | 18 | import java.lang.annotation.Retention; 19 | import java.lang.annotation.Target; 20 | 21 | import jakarta.enterprise.inject.Stereotype; 22 | import jakarta.transaction.Transactional; 23 | 24 | import org.omnifaces.services.pooled.Pooled; 25 | 26 | @Stereotype 27 | @Pooled 28 | @Transactional(rollbackOn = Throwable.class) 29 | @Target(TYPE) 30 | @Retention(RUNTIME) 31 | public @interface Service { 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/asynchronous/Asynchronous.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.asynchronous; 14 | 15 | import static java.lang.annotation.ElementType.METHOD; 16 | import static java.lang.annotation.ElementType.TYPE; 17 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 18 | 19 | import java.lang.annotation.Inherited; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.Target; 22 | 23 | import jakarta.enterprise.util.AnnotationLiteral; 24 | import jakarta.interceptor.InterceptorBinding; 25 | 26 | @InterceptorBinding 27 | @Target({ METHOD, TYPE }) 28 | @Retention(RUNTIME) 29 | @Inherited 30 | public @interface Asynchronous { 31 | 32 | /** 33 | * Supports inline instantiation of the {@link Asynchronous} annotation. 34 | * 35 | */ 36 | public static final class Literal extends AnnotationLiteral implements Asynchronous { 37 | private static final long serialVersionUID = 1L; 38 | 39 | /** 40 | * Instance of the {@link Asynchronous} annotation. 41 | */ 42 | public static final Literal INSTANCE = new Literal(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/asynchronous/AsynchronousInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.asynchronous; 14 | 15 | import static jakarta.interceptor.Interceptor.Priority.PLATFORM_BEFORE; 16 | 17 | import java.io.Serializable; 18 | 19 | import jakarta.annotation.Priority; 20 | import jakarta.enterprise.context.control.RequestContextController; 21 | import jakarta.inject.Inject; 22 | import jakarta.inject.Provider; 23 | import jakarta.interceptor.AroundInvoke; 24 | import jakarta.interceptor.Interceptor; 25 | import jakarta.interceptor.InvocationContext; 26 | 27 | @Interceptor 28 | @Asynchronous 29 | @Priority(PLATFORM_BEFORE + 1) 30 | public class AsynchronousInterceptor implements Serializable { 31 | 32 | private static final long serialVersionUID = 1L; 33 | 34 | @Inject 35 | private ExecutorBean executorBean; 36 | 37 | @Inject 38 | private Provider requestContextControllerProvider; 39 | 40 | @AroundInvoke 41 | public Object submitAsync(InvocationContext ctx) throws Exception { 42 | return new FutureDelegator(executorBean.getExecutorService().submit(() -> { 43 | RequestContextController requestContextController = requestContextControllerProvider.get(); 44 | requestContextController.activate(); 45 | try { 46 | return ctx.proceed(); 47 | } finally { 48 | requestContextController.deactivate(); 49 | } 50 | })); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/asynchronous/ExecutorBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.asynchronous; 14 | 15 | import java.util.concurrent.ExecutorService; 16 | import java.util.concurrent.Executors; 17 | 18 | import javax.naming.InitialContext; 19 | import javax.naming.NamingException; 20 | 21 | import jakarta.annotation.PostConstruct; 22 | import jakarta.enterprise.context.ApplicationScoped; 23 | 24 | @ApplicationScoped 25 | public class ExecutorBean { 26 | 27 | private ExecutorService executorService; 28 | 29 | @PostConstruct 30 | public void init() { 31 | executorService = createExecutorService(); 32 | } 33 | 34 | public ExecutorService getExecutorService() { 35 | return executorService; 36 | } 37 | 38 | private ExecutorService createExecutorService() { 39 | try { 40 | return (ExecutorService) new InitialContext().lookup("java:comp/env/concurrent/ThreadPool"); 41 | } catch (NamingException e) { 42 | // Ignore 43 | } 44 | 45 | return Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/asynchronous/FutureDelegator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.asynchronous; 14 | 15 | import java.util.concurrent.ExecutionException; 16 | import java.util.concurrent.Future; 17 | import java.util.concurrent.TimeUnit; 18 | import java.util.concurrent.TimeoutException; 19 | 20 | import jakarta.ejb.AsyncResult; 21 | 22 | public class FutureDelegator implements Future { 23 | 24 | private final Future future; 25 | 26 | public FutureDelegator(Future future) { 27 | this.future = future; 28 | } 29 | 30 | @Override 31 | public Object get() throws InterruptedException, ExecutionException { 32 | AsyncResult asyncResult = (AsyncResult) future.get(); 33 | if (asyncResult == null) { 34 | return null; 35 | } 36 | 37 | return asyncResult.get(); 38 | } 39 | 40 | @Override 41 | public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 42 | AsyncResult asyncResult = (AsyncResult) future.get(timeout, unit); 43 | if (asyncResult == null) { 44 | return null; 45 | } 46 | 47 | return asyncResult.get(); 48 | } 49 | 50 | @Override 51 | public boolean cancel(boolean mayInterruptIfRunning) { 52 | return future.cancel(mayInterruptIfRunning); 53 | } 54 | 55 | @Override 56 | public boolean isCancelled() { 57 | return future.isCancelled(); 58 | } 59 | 60 | @Override 61 | public boolean isDone() { 62 | return future.isDone(); 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/lock/Lock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.lock; 14 | 15 | import static java.lang.annotation.ElementType.METHOD; 16 | import static java.lang.annotation.ElementType.TYPE; 17 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 18 | import static java.util.concurrent.TimeUnit.SECONDS; 19 | import static org.omnifaces.services.lock.Lock.TimeoutType.TIMEOUT; 20 | import static org.omnifaces.services.lock.Lock.Type.WRITE; 21 | 22 | import java.lang.annotation.Inherited; 23 | import java.lang.annotation.Retention; 24 | import java.lang.annotation.Target; 25 | import java.util.concurrent.TimeUnit; 26 | 27 | import jakarta.enterprise.util.AnnotationLiteral; 28 | import jakarta.enterprise.util.Nonbinding; 29 | import jakarta.interceptor.InterceptorBinding; 30 | 31 | @InterceptorBinding 32 | @Target({ METHOD, TYPE }) 33 | @Retention(RUNTIME) 34 | @Inherited 35 | public @interface Lock { 36 | 37 | enum Type { 38 | /** 39 | * For read-only operations. Allows simultaneous access to methods designated as READ, as long as no 40 | * WRITE lock is held. 41 | */ 42 | READ, 43 | 44 | /** 45 | * For exclusive access to the bean instance. A WRITE lock can only be acquired when no other method with 46 | * either a READ or WRITE lock is currently held. 47 | */ 48 | WRITE 49 | } 50 | 51 | enum TimeoutType { 52 | /** 53 | * A timeout value in the units specified by the unit element. 54 | */ 55 | TIMEOUT, 56 | 57 | /** 58 | * Concurrent access is not permitted; so no wait therefore no timeout is allowed. 59 | * Either the lock is available and grabbed immediately, or an exception is thrown. 60 | */ 61 | NOT_PERMITTED, 62 | 63 | /** 64 | * The client request will block indefinitely until it can proceed; it waits for as long 65 | * as it needs to, hence the timeout is unlimited. 66 | */ 67 | INDEFINITTE 68 | } 69 | 70 | /** 71 | * 72 | * @return The Lock.Type to use 73 | */ 74 | @Nonbinding Type type() default WRITE; 75 | 76 | /** 77 | * 78 | * @return the way to deal with time out waiting for a lock 79 | */ 80 | @Nonbinding TimeoutType timeoutType() default TimeoutType.TIMEOUT; 81 | 82 | /** 83 | * 84 | * @return the time to wait to obtain a lock 85 | */ 86 | @Nonbinding long accessTimeout() default 60; 87 | 88 | /** 89 | * 90 | * @return units used for the specified accessTimeout value. 91 | */ 92 | @Nonbinding TimeUnit unit() default SECONDS; 93 | 94 | 95 | /** 96 | * Supports inline instantiation of the {@link Lock} annotation. 97 | * 98 | */ 99 | public static final class Literal extends AnnotationLiteral implements Lock { 100 | 101 | private static final long serialVersionUID = 1L; 102 | 103 | /** 104 | * Instance of the Literal annotation. 105 | */ 106 | public static final Literal INSTANCE = of(WRITE, TIMEOUT, 60, SECONDS); 107 | 108 | private final Type type; 109 | private final TimeoutType timeoutType; 110 | private final long accessTimeout; 111 | private final TimeUnit unit; 112 | 113 | public static Literal of(Type type, TimeoutType timeoutType, long accessTimeout, TimeUnit unit) { 114 | return new Literal(type, timeoutType, accessTimeout, unit); 115 | } 116 | 117 | private Literal(Type type, TimeoutType timeoutType, long accessTimeout, TimeUnit unit) { 118 | this.type = type; 119 | this.timeoutType = timeoutType; 120 | this.accessTimeout = accessTimeout; 121 | this.unit = unit; 122 | } 123 | 124 | @Override 125 | public Type type() { 126 | return type; 127 | } 128 | 129 | @Override 130 | public TimeoutType timeoutType() { 131 | return timeoutType; 132 | } 133 | 134 | @Override 135 | public long accessTimeout() { 136 | return accessTimeout; 137 | } 138 | 139 | @Override 140 | public TimeUnit unit() { 141 | return unit; 142 | } 143 | } 144 | 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/lock/LockInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.lock; 14 | 15 | import static jakarta.interceptor.Interceptor.Priority.PLATFORM_BEFORE; 16 | import static org.omnifaces.services.lock.Lock.Type.READ; 17 | 18 | import java.io.Serializable; 19 | import java.util.concurrent.locks.ReadWriteLock; 20 | import java.util.concurrent.locks.ReentrantReadWriteLock; 21 | 22 | import org.omnifaces.services.util.CdiUtils; 23 | 24 | import jakarta.annotation.Priority; 25 | import jakarta.enterprise.inject.Intercepted; 26 | import jakarta.enterprise.inject.spi.Bean; 27 | import jakarta.enterprise.inject.spi.BeanManager; 28 | import jakarta.inject.Inject; 29 | import jakarta.interceptor.AroundInvoke; 30 | import jakarta.interceptor.Interceptor; 31 | import jakarta.interceptor.InvocationContext; 32 | 33 | @Interceptor 34 | @Lock 35 | @Priority(PLATFORM_BEFORE) 36 | public class LockInterceptor implements Serializable { 37 | 38 | private static final long serialVersionUID = 1L; 39 | 40 | @Inject 41 | private BeanManager beanManager; 42 | 43 | @Inject 44 | @Intercepted 45 | private Bean interceptedBean; 46 | 47 | private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); 48 | 49 | @AroundInvoke 50 | public Object doLock(InvocationContext ctx) throws Exception { 51 | Lock lockAnnotation = getLockAnnotation(ctx); 52 | java.util.concurrent.locks.Lock lock = getReadOrWriteLock(lockAnnotation); 53 | 54 | acquireLock(lockAnnotation, lock); 55 | try { 56 | return ctx.proceed(); 57 | } finally { 58 | lock.unlock(); 59 | } 60 | } 61 | 62 | private java.util.concurrent.locks.Lock getReadOrWriteLock(Lock lockAnnotation) { 63 | return lockAnnotation.type() == READ? readWriteLock.readLock() : readWriteLock.writeLock(); 64 | } 65 | 66 | private Lock getLockAnnotation(InvocationContext ctx) { 67 | return CdiUtils.getInterceptorBindingAnnotation(ctx, beanManager, interceptedBean, Lock.class); 68 | } 69 | 70 | private void acquireLock(Lock lockAnnotation, java.util.concurrent.locks.Lock lock) throws InterruptedException { 71 | switch (lockAnnotation.timeoutType()) { 72 | case TIMEOUT: 73 | if (!lock.tryLock(lockAnnotation.accessTimeout(), lockAnnotation.unit())) { 74 | throw new IllegalStateException( 75 | "Could not obtain lock in " + 76 | lockAnnotation.accessTimeout() + " " + 77 | lockAnnotation.unit().name()); 78 | } 79 | break; 80 | 81 | case INDEFINITTE: 82 | lock.lock(); 83 | break; 84 | 85 | case NOT_PERMITTED: 86 | if (!lock.tryLock()) { 87 | throw new IllegalStateException("Lock already locked, and no wait allowed"); 88 | } 89 | break; 90 | } 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/pooled/PoolKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.pooled; 14 | 15 | import java.util.Objects; 16 | 17 | import jakarta.enterprise.context.spi.Contextual; 18 | 19 | final class PoolKey { 20 | 21 | private final Contextual contextual; 22 | private final int index; 23 | 24 | public PoolKey(Contextual contextual, int index) { 25 | this.contextual = contextual; 26 | this.index = index; 27 | } 28 | 29 | Contextual contextual() { 30 | return contextual; 31 | } 32 | 33 | int index() { 34 | return index; 35 | } 36 | 37 | @Override 38 | public boolean equals(Object o) { 39 | if (this == o) { 40 | return true; 41 | } 42 | if (o == null || getClass() != o.getClass()) { 43 | return false; 44 | } 45 | PoolKey poolKey = (PoolKey) o; 46 | return index == poolKey.index && 47 | Objects.equals(contextual, poolKey.contextual); 48 | } 49 | 50 | @Override 51 | public int hashCode() { 52 | return Objects.hash(contextual, index); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/pooled/PoolLockTimeoutException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.pooled; 14 | 15 | public class PoolLockTimeoutException extends RuntimeException { 16 | 17 | public PoolLockTimeoutException() { 18 | super(); 19 | } 20 | 21 | public PoolLockTimeoutException(String message) { 22 | super(message); 23 | } 24 | 25 | public PoolLockTimeoutException(String message, Throwable cause) { 26 | super(message, cause); 27 | } 28 | 29 | public PoolLockTimeoutException(Throwable cause) { 30 | super(cause); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/pooled/Pooled.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.pooled; 14 | 15 | import static java.lang.annotation.ElementType.METHOD; 16 | import static java.lang.annotation.ElementType.TYPE; 17 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 18 | import static java.util.concurrent.TimeUnit.MINUTES; 19 | 20 | import java.lang.annotation.Documented; 21 | import java.lang.annotation.Inherited; 22 | import java.lang.annotation.Retention; 23 | import java.lang.annotation.Target; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | import jakarta.enterprise.context.NormalScope; 27 | import jakarta.enterprise.util.AnnotationLiteral; 28 | 29 | /** 30 | * Specify that a bean is to be pooled. 31 | *

32 | * Pooled beans can have multiple instances that are shared between all threads of an application, but can never be 33 | * called by more than one thread at the same time. When a method call on a pooled bean is performed, an instance is 34 | * selected from the pool and is locked until the method call ends. When performing multiple method calls directly after 35 | * each other, multiple different beans may be used. 36 | *

37 | */ 38 | @Inherited 39 | @NormalScope 40 | @Documented 41 | @Target({TYPE, METHOD}) 42 | @Retention(RUNTIME) 43 | public @interface Pooled { 44 | 45 | /** 46 | * The maximum number of instances in the pool. 47 | * @return the maximum number of instances 48 | */ 49 | int maxNumberOfInstances() default 10; 50 | 51 | /** 52 | * The maximum amount of time to attempt to obtain a lock on an instance from the pool. 53 | * @return the maximum amount of time to try to obtain a lock 54 | */ 55 | long instanceLockTimeout() default 5; 56 | 57 | /** 58 | * The {@link TimeUnit} to use for the {@link #instanceLockTimeout()} 59 | * @return the time unit to use 60 | */ 61 | TimeUnit instanceLockTimeoutUnit() default MINUTES; 62 | 63 | /** 64 | * The types of {@link Throwable Throwables} which must cause the destruction of a pooled bean instance. 65 | *

66 | * If the pooled bean instances throws any Throwable which is an instance of a type set in the destroyOn property, 67 | * then the bean will be destroyed, except if the throwable is also an instance of a type set in the {@link 68 | * #dontDestroyOn()} property. 69 | *

70 | * 71 | * @return The types of {@link Throwable Throwables} which must cause the destruction of a pooled bean instance 72 | */ 73 | Class[] destroyOn() default {}; 74 | 75 | /** 76 | * The types of {@link Throwable Throwables} which must not cause the destruction of a pooled bean instance. 77 | *

78 | * If the {@link #destroyOn()} property is empty, but the dontDestroyOn property is not, then the bean will be 79 | * destroyed for any Throwable that isn't an instance of a type in the dontDestroyOn property. When the {@link #destroyOn()} property is not empty, then the dontDestroyOn property takes precedence. If a pooled bean instance throws a throwable which is an instance of a type in both the destroyOn and dontDestroyOn properties, then the bean will not be destroyed. 80 | *

81 | * 82 | * @return The types of {@link Throwable Throwables} which must not cause the destruction of a pooled bean instance. 83 | */ 84 | Class[] dontDestroyOn() default {}; 85 | 86 | /** 87 | * Supports inline instantiation of the Pooled annotation. 88 | * 89 | * @since 3.0 90 | */ 91 | public static final class Literal extends AnnotationLiteral implements Pooled { 92 | 93 | private static final long serialVersionUID = 1L; 94 | 95 | private final int maxNumberOfInstances; 96 | private final long instanceLockTimeout; 97 | private final TimeUnit instanceLockTimeoutUnit; 98 | private final Class[] destroyOn; 99 | private final Class[] dontDestroyOn; 100 | 101 | /** 102 | * Default instance of the {@link Pooled} annotation. 103 | */ 104 | public static final Literal INSTANCE = of( 105 | 10, 106 | 5, 107 | MINUTES, 108 | new Class[]{}, 109 | new Class[]{} 110 | ); 111 | 112 | /** 113 | * Instance of the {@link Pooled} annotation. 114 | * 115 | * @param maxNumberOfInstances The maximum number of instances in the pool. 116 | * @param instanceLockTimeout The maximum amount of time to attempt to obtain a lock on an instance from the pool. 117 | * @param instanceLockTimeoutUnit The {@link TimeUnit} to use for the {@link #instanceLockTimeout()} 118 | * @param destroyOn The types of {@link Throwable Throwables} which must cause the destruction of a pooled bean instance. 119 | * @param dontDestroyOn The types of {@link Throwable Throwables} which must not cause the destruction of a pooled bean instance. 120 | 121 | * @return instance of the {@link Pooled} annotation 122 | */ 123 | public static Literal of( 124 | final int maxNumberOfInstances, 125 | final long instanceLockTimeout, 126 | final TimeUnit instanceLockTimeoutUnit, 127 | final Class[] destroyOn, 128 | final Class[] dontDestroyOn 129 | 130 | 131 | ) { 132 | return new Literal( 133 | maxNumberOfInstances, 134 | instanceLockTimeout, 135 | instanceLockTimeoutUnit, 136 | destroyOn, 137 | dontDestroyOn 138 | ); 139 | } 140 | 141 | private Literal( 142 | final int maxNumberOfInstances, 143 | final long instanceLockTimeout, 144 | final TimeUnit instanceLockTimeoutUnit, 145 | final Class[] destroyOn, 146 | final Class[] dontDestroyOn 147 | ) { 148 | 149 | this.maxNumberOfInstances = maxNumberOfInstances; 150 | this.instanceLockTimeout = instanceLockTimeout; 151 | this.instanceLockTimeoutUnit = instanceLockTimeoutUnit; 152 | this.destroyOn = destroyOn; 153 | this.dontDestroyOn = dontDestroyOn; 154 | } 155 | 156 | 157 | @Override 158 | public int maxNumberOfInstances() { 159 | return maxNumberOfInstances; 160 | } 161 | 162 | @Override 163 | public long instanceLockTimeout() { 164 | return instanceLockTimeout; 165 | } 166 | 167 | @Override 168 | public TimeUnit instanceLockTimeoutUnit() { 169 | return instanceLockTimeoutUnit; 170 | } 171 | 172 | @Override 173 | public Class[] destroyOn() { 174 | return destroyOn; 175 | } 176 | 177 | @Override 178 | public Class[] dontDestroyOn() { 179 | return dontDestroyOn; 180 | } 181 | 182 | } 183 | } 184 | 185 | 186 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/pooled/PooledContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.pooled; 14 | 15 | import java.lang.annotation.Annotation; 16 | import java.util.Map; 17 | import java.util.concurrent.BlockingDeque; 18 | import java.util.concurrent.ConcurrentHashMap; 19 | import java.util.concurrent.LinkedBlockingDeque; 20 | import java.util.stream.IntStream; 21 | 22 | import jakarta.enterprise.context.spi.AlterableContext; 23 | import jakarta.enterprise.context.spi.Contextual; 24 | import jakarta.enterprise.context.spi.CreationalContext; 25 | import jakarta.enterprise.inject.spi.Bean; 26 | 27 | /** 28 | * The {@link AlterableContext} implementation for {@link Pooled} beans. 29 | */ 30 | public class PooledContext implements AlterableContext { 31 | 32 | private final ThreadLocal poolScope = ThreadLocal.withInitial(PooledScope::new); 33 | private final Map, InstancePool> instancePools = new ConcurrentHashMap<>(); 34 | 35 | private final Map, Object> dummyInstances = new ConcurrentHashMap<>(); 36 | 37 | @Override 38 | public Class getScope() { 39 | return Pooled.class; 40 | } 41 | 42 | @SuppressWarnings("unchecked") 43 | @Override 44 | public T get(Contextual contextual, CreationalContext creationalContext) { 45 | if (contextual instanceof Bean) { 46 | PoolKey poolKey = poolScope.get().getPoolKey(contextual); 47 | 48 | if (poolKey == null) { 49 | return (T) dummyInstances.computeIfAbsent(contextual, ctx -> contextual.create(creationalContext)); 50 | } 51 | 52 | return ((InstancePool) instancePools.get(poolKey.contextual())).getInstance(poolKey, creationalContext); 53 | } 54 | 55 | // TODO add clear error message and pick better exception 56 | throw new IllegalArgumentException(); 57 | } 58 | 59 | @SuppressWarnings("unchecked") 60 | @Override 61 | public T get(Contextual contextual) { 62 | if (contextual instanceof Bean) { 63 | PoolKey poolKey = poolScope.get().getPoolKey(contextual); 64 | 65 | if (poolKey == null) { 66 | return (T) dummyInstances.get(contextual); 67 | } 68 | 69 | return ((InstancePool) instancePools.get(poolKey.contextual())).getInstance(poolKey); 70 | } 71 | return null; 72 | } 73 | 74 | @Override 75 | public boolean isActive() { 76 | return true; 77 | } 78 | 79 | @SuppressWarnings("unchecked") 80 | PoolKey allocateBean(Contextual contextual) { 81 | PoolKey poolKey = ((InstancePool) instancePools.get(contextual)).allocateInstance(); 82 | 83 | poolScope.get().setPoolKey(poolKey); 84 | 85 | return poolKey; 86 | } 87 | 88 | @SuppressWarnings("unchecked") 89 | void releaseBean(PoolKey poolKey) { 90 | poolScope.get().removePoolKey(poolKey); 91 | 92 | ((InstancePool) instancePools.get(poolKey.contextual())).releaseInstance(poolKey); 93 | } 94 | 95 | boolean hasAllocatedInstanceOf(Bean bean) { 96 | return poolScope.get().getPoolKey(bean) != null; 97 | } 98 | 99 | public void createInstancePool(Contextual contextual, Pooled poolSettings) { 100 | instancePools.put(contextual, new InstancePool<>(contextual, poolSettings)); 101 | } 102 | 103 | boolean mustDestroyBeanWhenCaught(Contextual contextual, Throwable throwable) { 104 | return instancePools.get(contextual).mustDestroyBeanWhenCaught(throwable); 105 | } 106 | 107 | @Override 108 | public void destroy(Contextual contextual) { 109 | PoolKey poolKey = poolScope.get().getPoolKey(contextual); 110 | 111 | if (poolKey != null) { 112 | destroyInstance(poolKey); 113 | } 114 | } 115 | 116 | private void destroyInstance(PoolKey poolKey) { 117 | @SuppressWarnings("unchecked") 118 | InstancePool instancePool = (InstancePool) instancePools.get(poolKey.contextual()); 119 | 120 | instancePool.destroyInstance(poolKey); 121 | } 122 | 123 | private static class InstancePool { 124 | 125 | private final Contextual contextual; 126 | private final Pooled poolSettings; 127 | 128 | private final Map, Instance> instances = new ConcurrentHashMap<>(); 129 | private final BlockingDeque> freeInstanceKeys = new LinkedBlockingDeque<>(); 130 | 131 | InstancePool(Contextual contextual, Pooled poolSettings) { 132 | this.contextual = contextual; 133 | this.poolSettings = poolSettings; 134 | 135 | IntStream.range(0, poolSettings.maxNumberOfInstances()) 136 | .mapToObj(i -> new PoolKey<>(contextual, i)) 137 | .forEach(freeInstanceKeys::add); 138 | } 139 | 140 | T getInstance(PoolKey poolKey) { 141 | if (!poolKey.contextual().equals(contextual)) { 142 | throw new IllegalArgumentException(); 143 | } 144 | 145 | Instance instance = instances.get(poolKey); 146 | 147 | if (instance != null) { 148 | return instance.getInstance(); 149 | } 150 | 151 | return null; 152 | } 153 | 154 | T getInstance(PoolKey poolKey, CreationalContext context) { 155 | if (!poolKey.contextual().equals(contextual)) { 156 | throw new IllegalArgumentException(); 157 | } 158 | 159 | return instances.computeIfAbsent(poolKey, key -> new Instance<>(contextual, context)).getInstance(); 160 | } 161 | 162 | PoolKey allocateInstance() { 163 | try { 164 | PoolKey poolKey = freeInstanceKeys.poll(poolSettings.instanceLockTimeout(), poolSettings.instanceLockTimeoutUnit()); 165 | 166 | if (poolKey == null) { 167 | // Unable to allocate an instance within the configured timeout 168 | throw new PoolLockTimeoutException(); 169 | } 170 | 171 | return poolKey; 172 | } catch (InterruptedException e) { 173 | throw new UncheckedInterruptedException(e); 174 | } 175 | } 176 | 177 | void releaseInstance(PoolKey key) { 178 | if (!contextual.equals(key.contextual())) { 179 | throw new IllegalArgumentException(); 180 | } 181 | 182 | freeInstanceKeys.addFirst(key); 183 | } 184 | 185 | void destroyInstance(PoolKey key) { 186 | Instance instance = instances.remove(key); 187 | 188 | instance.destroy(contextual); 189 | } 190 | 191 | boolean mustDestroyBeanWhenCaught(Throwable throwable) { 192 | for (Class throwableType: poolSettings.dontDestroyOn()) { 193 | if (throwableType.isInstance(throwable)) { 194 | return false; 195 | } 196 | } 197 | 198 | if (poolSettings.dontDestroyOn().length > 0 && poolSettings.destroyOn().length == 0) { 199 | return true; 200 | } 201 | 202 | for (Class throwableType: poolSettings.destroyOn()) { 203 | if (throwableType.isInstance(throwable)) { 204 | return true; 205 | } 206 | } 207 | 208 | return false; 209 | } 210 | } 211 | 212 | private static class Instance { 213 | 214 | private final T instance; 215 | private final CreationalContext creationalContext; 216 | 217 | Instance(Contextual contextual, CreationalContext creationalContext) { 218 | this.instance = contextual.create(creationalContext); 219 | this.creationalContext = creationalContext; 220 | } 221 | 222 | T getInstance() { 223 | return instance; 224 | } 225 | 226 | CreationalContext getCreationalContext() { 227 | return creationalContext; 228 | } 229 | 230 | void destroy(Contextual contextual) { 231 | contextual.destroy(instance, creationalContext); 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/pooled/PooledScope.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.pooled; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import jakarta.enterprise.context.spi.Contextual; 19 | 20 | class PooledScope { 21 | 22 | private final Map, PoolKey> allocatedPoolKeys = new HashMap<>(); 23 | 24 | public void setPoolKey(PoolKey pookKey) { 25 | allocatedPoolKeys.put(pookKey.contextual(), pookKey); 26 | } 27 | 28 | public void removePoolKey(PoolKey poolKey) { 29 | allocatedPoolKeys.remove(poolKey.contextual()); 30 | } 31 | 32 | @SuppressWarnings("unchecked") 33 | public PoolKey getPoolKey(Contextual contextual) { 34 | return (PoolKey) allocatedPoolKeys.get(contextual); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/pooled/PooledScopeEnabled.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.pooled; 14 | 15 | import static java.lang.annotation.ElementType.METHOD; 16 | import static java.lang.annotation.ElementType.TYPE; 17 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 18 | 19 | import java.lang.annotation.Documented; 20 | import java.lang.annotation.Inherited; 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.Target; 23 | 24 | import org.omnifaces.services.asynchronous.Asynchronous; 25 | 26 | import jakarta.enterprise.util.AnnotationLiteral; 27 | import jakarta.interceptor.InterceptorBinding; 28 | 29 | @InterceptorBinding 30 | @Documented 31 | @Inherited 32 | @Retention(RUNTIME) 33 | @Target({TYPE, METHOD}) 34 | public @interface PooledScopeEnabled { 35 | 36 | /** 37 | * Supports inline instantiation of the {@link PooledScopeEnabled} annotation. 38 | * 39 | */ 40 | public static final class Literal extends AnnotationLiteral implements PooledScopeEnabled { 41 | private static final long serialVersionUID = 1L; 42 | 43 | /** 44 | * Instance of the {@link Asynchronous} annotation. 45 | */ 46 | public static final Literal INSTANCE = new Literal(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/pooled/PooledScopeInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.pooled; 14 | 15 | import static jakarta.interceptor.Interceptor.Priority.PLATFORM_BEFORE; 16 | 17 | import java.lang.reflect.InvocationTargetException; 18 | 19 | import jakarta.annotation.Priority; 20 | import jakarta.enterprise.context.spi.CreationalContext; 21 | import jakarta.enterprise.inject.Intercepted; 22 | import jakarta.enterprise.inject.spi.Bean; 23 | import jakarta.enterprise.inject.spi.BeanManager; 24 | import jakarta.inject.Inject; 25 | import jakarta.interceptor.AroundInvoke; 26 | import jakarta.interceptor.Interceptor; 27 | import jakarta.interceptor.InvocationContext; 28 | 29 | @Interceptor 30 | @Priority(PLATFORM_BEFORE) 31 | @PooledScopeEnabled 32 | public class PooledScopeInterceptor { 33 | 34 | @Inject 35 | private BeanManager beanManager; 36 | 37 | @Inject 38 | @Intercepted 39 | private Bean interceptedBean; 40 | 41 | @AroundInvoke 42 | public Object aroundInvoke(InvocationContext ctx) throws Throwable { 43 | PooledContext context = (PooledContext) beanManager.getContext(Pooled.class); 44 | 45 | if (context.hasAllocatedInstanceOf(interceptedBean)) { 46 | return ctx.proceed(); 47 | } 48 | 49 | PoolKey poolKey = context.allocateBean(interceptedBean); 50 | 51 | try { 52 | CreationalContext creationalContext = beanManager.createCreationalContext(interceptedBean); 53 | Object reference = beanManager.getReference(interceptedBean, interceptedBean.getBeanClass(), creationalContext); 54 | 55 | return proceedOnInstance(ctx, reference); 56 | } 57 | catch (Throwable t) { 58 | destroyBeanIfNeeded(context, t); 59 | 60 | throw t; 61 | } 62 | finally { 63 | context.releaseBean(poolKey); 64 | } 65 | } 66 | 67 | private Object proceedOnInstance(InvocationContext ctx, Object instance) throws Throwable { 68 | try { 69 | return ctx.getMethod().invoke(instance, ctx.getParameters()); 70 | } 71 | catch(InvocationTargetException e) { 72 | throw e.getCause(); 73 | } 74 | } 75 | 76 | private void destroyBeanIfNeeded(PooledContext pooledContext, Throwable throwable) throws Throwable { 77 | if (pooledContext.mustDestroyBeanWhenCaught(interceptedBean, throwable)) { 78 | pooledContext.destroy(interceptedBean); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/pooled/UncheckedInterruptedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.pooled; 14 | 15 | // TODO move to OmniUtils?? 16 | public class UncheckedInterruptedException extends RuntimeException { 17 | 18 | public UncheckedInterruptedException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public UncheckedInterruptedException(Throwable cause) { 23 | super(cause); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/util/AnnotatedMethodWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.util; 14 | 15 | import java.lang.annotation.Annotation; 16 | import java.lang.reflect.Method; 17 | import java.lang.reflect.Type; 18 | import java.util.Collections; 19 | import java.util.HashSet; 20 | import java.util.List; 21 | import java.util.Set; 22 | 23 | import jakarta.enterprise.inject.spi.AnnotatedMethod; 24 | import jakarta.enterprise.inject.spi.AnnotatedParameter; 25 | import jakarta.enterprise.inject.spi.AnnotatedType; 26 | 27 | public class AnnotatedMethodWrapper implements AnnotatedMethod { 28 | 29 | private AnnotatedMethod wrappedAnnotatedMethod; 30 | 31 | private Set annotations; 32 | 33 | public AnnotatedMethodWrapper(AnnotatedMethod wrappedAnnotatedMethod) { 34 | this.wrappedAnnotatedMethod = wrappedAnnotatedMethod; 35 | 36 | annotations = new HashSet<>(wrappedAnnotatedMethod.getAnnotations()); 37 | } 38 | 39 | @Override 40 | public List> getParameters() { 41 | return wrappedAnnotatedMethod.getParameters(); 42 | } 43 | 44 | @Override 45 | public AnnotatedType getDeclaringType() { 46 | return wrappedAnnotatedMethod.getDeclaringType(); 47 | } 48 | 49 | @Override 50 | public boolean isStatic() { 51 | return wrappedAnnotatedMethod.isStatic(); 52 | } 53 | 54 | @Override 55 | public T getAnnotation(Class annotationType) { 56 | for (Annotation annotation : annotations) { 57 | if (annotationType.isInstance(annotation)) { 58 | return annotationType.cast(annotation); 59 | } 60 | } 61 | 62 | return null; 63 | } 64 | 65 | @Override 66 | public Set getAnnotations() { 67 | return Collections.unmodifiableSet(annotations); 68 | } 69 | 70 | @Override 71 | public Type getBaseType() { 72 | return wrappedAnnotatedMethod.getBaseType(); 73 | } 74 | 75 | @Override 76 | public Set getTypeClosure() { 77 | return wrappedAnnotatedMethod.getTypeClosure(); 78 | } 79 | 80 | @Override 81 | public boolean isAnnotationPresent(Class annotationType) { 82 | for (Annotation annotation : annotations) { 83 | if (annotationType.isInstance(annotation)) { 84 | return true; 85 | } 86 | } 87 | 88 | return false; 89 | } 90 | 91 | @Override 92 | public Method getJavaMember() { 93 | return wrappedAnnotatedMethod.getJavaMember(); 94 | } 95 | 96 | public void addAnnotation(Annotation annotation) { 97 | annotations.add(annotation); 98 | } 99 | 100 | public void removeAnnotation(Annotation annotation) { 101 | annotations.remove(annotation); 102 | } 103 | 104 | public void removeAnnotation(Class annotationType) { 105 | Annotation annotation = getAnnotation(annotationType); 106 | if (annotation != null ) { 107 | removeAnnotation(annotation); 108 | } 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/util/AnnotatedTypeWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.util; 14 | 15 | import java.lang.annotation.Annotation; 16 | import java.lang.reflect.Type; 17 | import java.util.HashSet; 18 | import java.util.Set; 19 | 20 | import jakarta.enterprise.inject.spi.AnnotatedConstructor; 21 | import jakarta.enterprise.inject.spi.AnnotatedField; 22 | import jakarta.enterprise.inject.spi.AnnotatedMethod; 23 | import jakarta.enterprise.inject.spi.AnnotatedType; 24 | 25 | public class AnnotatedTypeWrapper implements AnnotatedType { 26 | 27 | private AnnotatedType wrappedAnnotatedType; 28 | 29 | private Set annotations = new HashSet<>(); 30 | private Set> annotatedMethods = new HashSet<>(); 31 | private Set> annotatedFields = new HashSet<>(); 32 | 33 | public AnnotatedTypeWrapper(AnnotatedType wrappedAnnotatedType) { 34 | this.wrappedAnnotatedType = wrappedAnnotatedType; 35 | 36 | annotations.addAll(wrappedAnnotatedType.getAnnotations()); 37 | annotatedMethods.addAll(wrappedAnnotatedType.getMethods()); 38 | annotatedFields.addAll(wrappedAnnotatedType.getFields()); 39 | } 40 | 41 | @Override 42 | public A getAnnotation(Class annotationType) { 43 | return wrappedAnnotatedType.getAnnotation(annotationType); 44 | } 45 | 46 | @Override 47 | public Set getAnnotations() { 48 | return annotations; 49 | } 50 | 51 | @Override 52 | public Type getBaseType() { 53 | return wrappedAnnotatedType.getBaseType(); 54 | } 55 | 56 | @Override 57 | public Set> getConstructors() { 58 | return wrappedAnnotatedType.getConstructors(); 59 | } 60 | 61 | @Override 62 | public Set> getFields() { 63 | return annotatedFields; 64 | } 65 | 66 | @Override 67 | public Class getJavaClass() { 68 | return wrappedAnnotatedType.getJavaClass(); 69 | } 70 | 71 | @Override 72 | public Set> getMethods() { 73 | return annotatedMethods; 74 | } 75 | 76 | @Override 77 | public Set getTypeClosure() { 78 | return wrappedAnnotatedType.getTypeClosure(); 79 | } 80 | 81 | @Override 82 | public boolean isAnnotationPresent(Class annotationType) { 83 | for (Annotation annotation : annotations) { 84 | if (annotationType.isInstance(annotation)) { 85 | return true; 86 | } 87 | } 88 | 89 | return false; 90 | } 91 | 92 | public void addAnnotation(Annotation annotation) { 93 | annotations.add(annotation); 94 | } 95 | 96 | public void removeAnnotation(Annotation annotation) { 97 | annotations.remove(annotation); 98 | } 99 | 100 | public void removeAnnotation(Class annotationType) { 101 | Annotation annotation = getAnnotation(annotationType); 102 | if (annotation != null ) { 103 | removeAnnotation(annotation); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/services/util/CdiUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.services.util; 14 | 15 | import java.lang.annotation.Annotation; 16 | import java.util.Arrays; 17 | import java.util.LinkedList; 18 | import java.util.Optional; 19 | import java.util.Queue; 20 | import java.util.Set; 21 | 22 | import jakarta.enterprise.inject.spi.Annotated; 23 | import jakarta.enterprise.inject.spi.Bean; 24 | import jakarta.enterprise.inject.spi.BeanManager; 25 | import jakarta.interceptor.InvocationContext; 26 | 27 | public class CdiUtils { 28 | 29 | public static T getInterceptorBindingAnnotation(InvocationContext invocationContext, BeanManager beanManager, Bean interceptedBean, Class type) { 30 | Optional optionalAnnotation = getAnnotation(beanManager, interceptedBean.getBeanClass(), (Class) type); 31 | if (optionalAnnotation.isPresent()) { 32 | return optionalAnnotation.get(); 33 | } 34 | 35 | @SuppressWarnings("unchecked") 36 | Set bindings = (Set) invocationContext.getContextData().get("org.jboss.weld.interceptor.bindings"); 37 | if (bindings != null) { 38 | optionalAnnotation = bindings.stream() 39 | .filter(annotation -> annotation.annotationType().equals(type)) 40 | .findAny() 41 | .map(annotation -> type.cast(annotation)); 42 | 43 | if (optionalAnnotation.isPresent()) { 44 | return optionalAnnotation.get(); 45 | } 46 | } 47 | 48 | throw new IllegalStateException("@" + type + " not present on " + interceptedBean.getBeanClass()); 49 | } 50 | 51 | public static Optional getAnnotation(BeanManager beanManager, Class annotatedClass, Class annotationType) { 52 | 53 | if (annotatedClass.isAnnotationPresent(annotationType)) { 54 | return Optional.of(annotatedClass.getAnnotation(annotationType)); 55 | } 56 | 57 | Queue annotations = new LinkedList<>(Arrays.asList(annotatedClass.getAnnotations())); 58 | 59 | while (!annotations.isEmpty()) { 60 | Annotation annotation = annotations.remove(); 61 | 62 | if (annotation.annotationType().equals(annotationType)) { 63 | return Optional.of(annotationType.cast(annotation)); 64 | } 65 | 66 | if (beanManager.isStereotype(annotation.annotationType())) { 67 | annotations.addAll( 68 | beanManager.getStereotypeDefinition( 69 | annotation.annotationType() 70 | ) 71 | ); 72 | } 73 | } 74 | 75 | return Optional.empty(); 76 | } 77 | 78 | public static Optional getAnnotation(BeanManager beanManager, Annotated annotated, Class annotationType) { 79 | 80 | annotated.getAnnotation(annotationType); 81 | 82 | if (annotated.getAnnotations().isEmpty()) { 83 | return Optional.empty(); 84 | } 85 | 86 | if (annotated.isAnnotationPresent(annotationType)) { 87 | return Optional.of(annotated.getAnnotation(annotationType)); 88 | } 89 | 90 | Queue annotations = new LinkedList<>(annotated.getAnnotations()); 91 | 92 | while (!annotations.isEmpty()) { 93 | Annotation annotation = annotations.remove(); 94 | 95 | if (annotation.annotationType().equals(annotationType)) { 96 | return Optional.of(annotationType.cast(annotation)); 97 | } 98 | 99 | if (beanManager.isStereotype(annotation.annotationType())) { 100 | annotations.addAll( 101 | beanManager.getStereotypeDefinition( 102 | annotation.annotationType() 103 | ) 104 | ); 105 | } 106 | } 107 | 108 | return Optional.empty(); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension: -------------------------------------------------------------------------------- 1 | org.omnifaces.services.CdiExtension -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/test/services/pooled/PooledTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.test.services.pooled; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 17 | import static org.junit.jupiter.api.Assertions.assertThrows; 18 | 19 | import org.jboss.arquillian.junit5.ArquillianExtension; 20 | import org.junit.jupiter.api.Disabled; 21 | import org.junit.jupiter.api.DisplayName; 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.extension.ExtendWith; 24 | 25 | import jakarta.inject.Inject; 26 | 27 | @ExtendWith(ArquillianExtension.class) 28 | @DisplayName("@Pooled") 29 | @Disabled("Complains about DependencyInjectionArquillianExtension and unnamed module") 30 | public class PooledTest { 31 | 32 | // @Deployment 33 | // public static Archive createDeployment() { 34 | // return create(WebArchive.class) 35 | // .addAsManifestResource(INSTANCE, "beans.xml") 36 | // .addClasses(SingleInstancePooledBean.class) 37 | // .addAsLibraries(create(JavaArchive.class) 38 | // .addAsManifestResource(INSTANCE, "beans.xml") 39 | // .addAsServiceProvider(Extension.class, CdiExtension.class) 40 | // .addPackages(true, "org.omnifaces.services.pooled") 41 | // .addPackages(true, "org.omnifaces.services.util") 42 | // ) 43 | // .addAsLibraries(Maven.resolver() 44 | // .loadPomFromFile("pom.xml") 45 | // .resolve("org.omnifaces:omniutils") 46 | // .withoutTransitivity() 47 | // .asSingleFile()); 48 | // } 49 | 50 | @Inject 51 | private SingleInstancePooledBean singleInstancePooledBean; 52 | 53 | @Test 54 | @DisplayName("with a single instance configured will always returns the same instance.") 55 | public void bean_withASingleInstanceConfigured_willAlwaysCauseSameInstanceToBeUsed() { 56 | int identityHashCode = singleInstancePooledBean.getIdentityHashCode(); 57 | 58 | assertEquals(identityHashCode, singleInstancePooledBean.getIdentityHashCode()); 59 | } 60 | 61 | @Test 62 | @DisplayName( 63 | "with an invocation that throws an exception not in the destroyOn field does not destroy the instance.") 64 | public void bean_withInvocationThrowingANonDestroyingException_doesNotDestroyInstance() { 65 | int identityHashCode = singleInstancePooledBean.getIdentityHashCode(); 66 | 67 | assertThrows(Exception.class, () -> singleInstancePooledBean.throwException(Exception::new)); 68 | 69 | assertEquals(identityHashCode, singleInstancePooledBean.getIdentityHashCode()); 70 | } 71 | 72 | @Test 73 | @DisplayName( 74 | "with an invocation that throws an exception explicitly listed in the dontDestroyOn field does not destroy the instance.") 75 | public void bean_withInvocationThrowingAnExplicitNonDestroyingException_doesNotDestroyInstance() { 76 | int identityHashCode = singleInstancePooledBean.getIdentityHashCode(); 77 | 78 | assertThrows(IllegalArgumentException.class, 79 | () -> singleInstancePooledBean.throwException(IllegalArgumentException::new)); 80 | 81 | assertEquals(identityHashCode, singleInstancePooledBean.getIdentityHashCode()); 82 | } 83 | 84 | 85 | @Test 86 | @DisplayName( 87 | "with an invocation that throws an exception explicitly listed in the destroyOn field destroys the instance.") 88 | public void bean_withInvocationThrowingAnExplicitDestroyingException_destroysInstance() { 89 | int identityHashCode = singleInstancePooledBean.getIdentityHashCode(); 90 | 91 | assertThrows(RuntimeException.class, () -> singleInstancePooledBean.throwException(RuntimeException::new)); 92 | 93 | assertNotEquals(identityHashCode, singleInstancePooledBean.getIdentityHashCode()); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/test/services/pooled/SingleInstancePooledBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.test.services.pooled; 14 | 15 | import java.util.function.Supplier; 16 | 17 | import org.omnifaces.services.pooled.Pooled; 18 | 19 | @Pooled(destroyOn = RuntimeException.class, dontDestroyOn = IllegalArgumentException.class, maxNumberOfInstances = 1) 20 | public class SingleInstancePooledBean { 21 | 22 | public int getIdentityHashCode() { 23 | return System.identityHashCode(this); 24 | } 25 | 26 | public void throwException(Supplier exceptionSupplier) throws E { 27 | throw exceptionSupplier.get(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/test/services/pooled/testing/DependencyInjectionArquillianExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.test.services.pooled.testing; 14 | 15 | import java.util.Optional; 16 | import java.util.function.Predicate; 17 | 18 | import jakarta.enterprise.inject.spi.BeanManager; 19 | import jakarta.enterprise.inject.spi.CDI; 20 | import jakarta.enterprise.inject.spi.InjectionTarget; 21 | 22 | import org.junit.jupiter.api.extension.ExtensionContext; 23 | import org.junit.jupiter.api.extension.TestInstancePostProcessor; 24 | 25 | /** 26 | * JUnit extension to enable CDI based dependency injection in test instances when running in an Arquillian container. 27 | */ 28 | public class DependencyInjectionArquillianExtension implements TestInstancePostProcessor { 29 | 30 | private static final Predicate isInsideArquillian = 31 | (context) -> context.getConfigurationParameter("insideArquillian").map(Boolean::parseBoolean).orElse(false); 32 | 33 | @Override 34 | public void postProcessTestInstance(Object testInstance, ExtensionContext extensionContext) { 35 | getCdi(extensionContext).ifPresent(cdi -> { 36 | var beanManager = cdi.getBeanManager(); 37 | var testClassType = beanManager.createAnnotatedType(testInstance.getClass()); 38 | var injectionTargetFactory = beanManager.getInjectionTargetFactory(testClassType); 39 | var injectionTarget = injectionTargetFactory.createInjectionTarget(null); 40 | 41 | inject(injectionTarget, beanManager, testInstance); 42 | }); 43 | } 44 | 45 | @SuppressWarnings("unchecked") 46 | private static void inject(InjectionTarget injectionTarget, BeanManager beanManager, Object instance) { 47 | injectionTarget.inject((T) instance, beanManager.createCreationalContext(null)); 48 | } 49 | 50 | private static Optional> getCdi(ExtensionContext context) { 51 | if (context.getConfigurationParameter("insideArquillian").map(Boolean::parseBoolean).orElse(false)) { 52 | return Optional.of(CDI.current()); 53 | } 54 | 55 | return Optional.empty(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/resources/arquillian.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | xml 24 | ${arquillian.liberty.wlpHome} 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/test/resources/server.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | jakartaee-9.1 21 | localConnector-1.0 22 | 23 | 24 | 25 | 26 | 29 | 32 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | --------------------------------------------------------------------------------