Repository: alipay/sofa-ark Branch: master Commit: 2d84233e2e2f Files: 625 Total size: 88.8 MB Directory structure: gitextract_hhlu8y0b/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── feature_request.md │ │ └── question_or_bug_report.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── cloud_code_scan.yml │ ├── inactive_issues_robot.yml │ ├── linux_unit_test.yml │ ├── release.yml │ ├── snapshot.yml │ └── windows_unit_test.yml ├── .gitignore ├── .travis.yml ├── CLAUDE.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Formatter.xml ├── HEADER ├── LICENSE ├── README.md ├── README_EN.md ├── SECURITY.md ├── change_version.sh ├── check_format.sh ├── codecov.yml ├── pom.xml ├── sofa-ark-bom/ │ ├── CLAUDE.md │ └── pom.xml ├── sofa-ark-parent/ │ ├── assembly/ │ │ ├── CLAUDE.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── assembly/ │ │ ├── assembly.xml │ │ └── mark │ ├── core/ │ │ ├── api/ │ │ │ ├── CLAUDE.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── alipay/ │ │ │ │ └── sofa/ │ │ │ │ └── ark/ │ │ │ │ └── api/ │ │ │ │ ├── ArkClient.java │ │ │ │ ├── ArkConfigs.java │ │ │ │ ├── ClientResponse.java │ │ │ │ └── ResponseCode.java │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alipay/ │ │ │ │ └── sofa/ │ │ │ │ └── ark/ │ │ │ │ └── api/ │ │ │ │ └── ArkConfigsTest.java │ │ │ └── resources/ │ │ │ └── test.props │ │ ├── common/ │ │ │ ├── CLAUDE.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── alipay/ │ │ │ │ │ └── sofa/ │ │ │ │ │ └── ark/ │ │ │ │ │ └── common/ │ │ │ │ │ ├── adapter/ │ │ │ │ │ │ └── ArkLogbackContextSelector.java │ │ │ │ │ ├── guice/ │ │ │ │ │ │ └── AbstractArkGuiceModule.java │ │ │ │ │ ├── log/ │ │ │ │ │ │ ├── ArkLogger.java │ │ │ │ │ │ └── ArkLoggerFactory.java │ │ │ │ │ ├── thread/ │ │ │ │ │ │ ├── CommonThreadPool.java │ │ │ │ │ │ ├── NamedThreadFactory.java │ │ │ │ │ │ └── ThreadPoolManager.java │ │ │ │ │ └── util/ │ │ │ │ │ ├── AssertUtils.java │ │ │ │ │ ├── BizIdentityUtils.java │ │ │ │ │ ├── ClassLoaderUtils.java │ │ │ │ │ ├── ClassUtils.java │ │ │ │ │ ├── EnvironmentUtils.java │ │ │ │ │ ├── FileUtils.java │ │ │ │ │ ├── OrderComparator.java │ │ │ │ │ ├── ParseUtils.java │ │ │ │ │ ├── PortSelectUtils.java │ │ │ │ │ ├── ReflectionUtils.java │ │ │ │ │ ├── SimpleByteBuffer.java │ │ │ │ │ ├── StringUtils.java │ │ │ │ │ └── ThreadPoolUtils.java │ │ │ │ └── resources/ │ │ │ │ └── com/ │ │ │ │ └── alipay/ │ │ │ │ └── sofa/ │ │ │ │ └── ark/ │ │ │ │ └── log/ │ │ │ │ ├── log4j/ │ │ │ │ │ └── log-conf.xml │ │ │ │ ├── log4j2/ │ │ │ │ │ └── log-conf.xml │ │ │ │ └── logback/ │ │ │ │ └── log-conf.xml │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alipay/ │ │ │ │ └── sofa/ │ │ │ │ └── ark/ │ │ │ │ └── common/ │ │ │ │ ├── adapter/ │ │ │ │ │ └── ArkLogbackContextSelectorTest.java │ │ │ │ ├── thread/ │ │ │ │ │ └── CommonThreadPoolTest.java │ │ │ │ └── util/ │ │ │ │ ├── AssertUtilsTest.java │ │ │ │ ├── BizIdentityUtilsTest.java │ │ │ │ ├── ClassLoaderUtilTest.java │ │ │ │ ├── ClassUtilsTest.java │ │ │ │ ├── FileUtilsTest.java │ │ │ │ ├── ParseUtilsTest.java │ │ │ │ ├── PortSelectUtilsTest.java │ │ │ │ ├── SimpleByteBufferTest.java │ │ │ │ └── StringUtilsTest.java │ │ │ └── resources/ │ │ │ ├── plugins/ │ │ │ │ └── sample-skywalking-agent-plugin.jar │ │ │ ├── sample-biz.jar │ │ │ └── sample-skywalking-agent.jar │ │ ├── exception/ │ │ │ ├── CLAUDE.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alipay/ │ │ │ └── sofa/ │ │ │ └── ark/ │ │ │ └── exception/ │ │ │ ├── ArkLoaderException.java │ │ │ └── ArkRuntimeException.java │ │ ├── pom.xml │ │ └── spi/ │ │ ├── CLAUDE.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alipay/ │ │ │ └── sofa/ │ │ │ └── ark/ │ │ │ └── spi/ │ │ │ ├── archive/ │ │ │ │ ├── AbstractArchive.java │ │ │ │ ├── Archive.java │ │ │ │ ├── BizArchive.java │ │ │ │ ├── ContainerArchive.java │ │ │ │ ├── ExecutableArchive.java │ │ │ │ └── PluginArchive.java │ │ │ ├── argument/ │ │ │ │ ├── CommandArgument.java │ │ │ │ └── LaunchCommand.java │ │ │ ├── command/ │ │ │ │ └── Command.java │ │ │ ├── constant/ │ │ │ │ └── Constants.java │ │ │ ├── event/ │ │ │ │ ├── AbstractArkEvent.java │ │ │ │ ├── AfterFinishDeployEvent.java │ │ │ │ ├── AfterFinishStartupEvent.java │ │ │ │ ├── ArkEvent.java │ │ │ │ ├── biz/ │ │ │ │ │ ├── AfterAllBizStartupEvent.java │ │ │ │ │ ├── AfterBizStartupEvent.java │ │ │ │ │ ├── AfterBizStartupFailedEvent.java │ │ │ │ │ ├── AfterBizStopEvent.java │ │ │ │ │ ├── AfterBizStopFailedEvent.java │ │ │ │ │ ├── AfterBizSwitchEvent.java │ │ │ │ │ ├── BeforeBizRecycleEvent.java │ │ │ │ │ ├── BeforeBizStartupEvent.java │ │ │ │ │ ├── BeforeBizStopEvent.java │ │ │ │ │ └── BeforeBizSwitchEvent.java │ │ │ │ └── plugin/ │ │ │ │ ├── AfterPluginStartupEvent.java │ │ │ │ ├── AfterPluginStopEvent.java │ │ │ │ ├── BeforePluginStartupEvent.java │ │ │ │ └── BeforePluginStopEvent.java │ │ │ ├── ext/ │ │ │ │ ├── ExtResponse.java │ │ │ │ └── ExtServiceProvider.java │ │ │ ├── model/ │ │ │ │ ├── Biz.java │ │ │ │ ├── BizConfig.java │ │ │ │ ├── BizInfo.java │ │ │ │ ├── BizOperation.java │ │ │ │ ├── BizState.java │ │ │ │ ├── Plugin.java │ │ │ │ ├── PluginConfig.java │ │ │ │ ├── PluginContext.java │ │ │ │ └── PluginOperation.java │ │ │ ├── pipeline/ │ │ │ │ ├── Pipeline.java │ │ │ │ ├── PipelineContext.java │ │ │ │ └── PipelineStage.java │ │ │ ├── registry/ │ │ │ │ ├── ServiceFilter.java │ │ │ │ ├── ServiceMetadata.java │ │ │ │ ├── ServiceProvider.java │ │ │ │ ├── ServiceProviderType.java │ │ │ │ └── ServiceReference.java │ │ │ ├── replay/ │ │ │ │ ├── Replay.java │ │ │ │ └── ReplayContext.java │ │ │ ├── service/ │ │ │ │ ├── ArkInject.java │ │ │ │ ├── ArkService.java │ │ │ │ ├── PluginActivator.java │ │ │ │ ├── PriorityOrdered.java │ │ │ │ ├── biz/ │ │ │ │ │ ├── AddBizToStaticDeployHook.java │ │ │ │ │ ├── BizDeployService.java │ │ │ │ │ ├── BizDeployer.java │ │ │ │ │ ├── BizFactoryService.java │ │ │ │ │ └── BizManagerService.java │ │ │ │ ├── classloader/ │ │ │ │ │ ├── ClassLoaderHook.java │ │ │ │ │ └── ClassLoaderService.java │ │ │ │ ├── event/ │ │ │ │ │ ├── EventAdminService.java │ │ │ │ │ └── EventHandler.java │ │ │ │ ├── extension/ │ │ │ │ │ ├── ArkServiceLoader.java │ │ │ │ │ ├── Extensible.java │ │ │ │ │ ├── Extension.java │ │ │ │ │ ├── ExtensionClass.java │ │ │ │ │ └── ExtensionLoaderService.java │ │ │ │ ├── injection/ │ │ │ │ │ └── InjectionService.java │ │ │ │ ├── plugin/ │ │ │ │ │ ├── PluginDeployService.java │ │ │ │ │ ├── PluginFactoryService.java │ │ │ │ │ └── PluginManagerService.java │ │ │ │ ├── registry/ │ │ │ │ │ └── RegistryService.java │ │ │ │ └── session/ │ │ │ │ ├── CommandProvider.java │ │ │ │ └── TelnetServerService.java │ │ │ └── web/ │ │ │ ├── AbstractEmbeddedServerService.java │ │ │ └── EmbeddedServerService.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alipay/ │ │ │ └── sofa/ │ │ │ └── ark/ │ │ │ └── spi/ │ │ │ ├── argument/ │ │ │ │ └── LaunchCommandTest.java │ │ │ ├── constant/ │ │ │ │ └── ConstantsTest.java │ │ │ ├── ext/ │ │ │ │ └── ExtResponseTest.java │ │ │ ├── model/ │ │ │ │ └── BizOperationTest.java │ │ │ ├── replay/ │ │ │ │ └── ReplayContextTest.java │ │ │ └── service/ │ │ │ └── extension/ │ │ │ └── ExtensionClassTest.java │ │ └── resources/ │ │ └── test 2.jar │ ├── core-impl/ │ │ ├── archive/ │ │ │ ├── CLAUDE.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── alipay/ │ │ │ │ └── sofa/ │ │ │ │ └── ark/ │ │ │ │ ├── bootstrap/ │ │ │ │ │ ├── AbstractLauncher.java │ │ │ │ │ ├── AgentClassLoader.java │ │ │ │ │ ├── ArkLauncher.java │ │ │ │ │ ├── BaseExecutableArchiveLauncher.java │ │ │ │ │ ├── ClasspathLauncher.java │ │ │ │ │ ├── ContainerClassLoader.java │ │ │ │ │ ├── MainMethodRunner.java │ │ │ │ │ └── UseFastConnectionExceptionsEnumeration.java │ │ │ │ └── loader/ │ │ │ │ ├── DirectoryBizArchive.java │ │ │ │ ├── DirectoryContainerArchive.java │ │ │ │ ├── EmbedClassPathArchive.java │ │ │ │ ├── ExecutableArkBizJar.java │ │ │ │ ├── ExplodedBizArchive.java │ │ │ │ ├── JarBizArchive.java │ │ │ │ ├── JarContainerArchive.java │ │ │ │ ├── JarPluginArchive.java │ │ │ │ ├── archive/ │ │ │ │ │ ├── ExplodedArchive.java │ │ │ │ │ └── JarFileArchive.java │ │ │ │ ├── data/ │ │ │ │ │ ├── RandomAccessData.java │ │ │ │ │ └── RandomAccessDataFile.java │ │ │ │ ├── jar/ │ │ │ │ │ ├── AsciiBytes.java │ │ │ │ │ ├── Bytes.java │ │ │ │ │ ├── CentralDirectoryEndRecord.java │ │ │ │ │ ├── CentralDirectoryFileHeader.java │ │ │ │ │ ├── CentralDirectoryParser.java │ │ │ │ │ ├── CentralDirectoryVisitor.java │ │ │ │ │ ├── FileHeader.java │ │ │ │ │ ├── Handler.java │ │ │ │ │ ├── JarEntry.java │ │ │ │ │ ├── JarEntryFilter.java │ │ │ │ │ ├── JarFile.java │ │ │ │ │ ├── JarFileEntries.java │ │ │ │ │ ├── JarURLConnection.java │ │ │ │ │ ├── JarUtils.java │ │ │ │ │ └── ZipInflaterInputStream.java │ │ │ │ └── util/ │ │ │ │ └── ModifyPathUtils.java │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alipay/ │ │ │ │ └── sofa/ │ │ │ │ └── ark/ │ │ │ │ ├── bootstrap/ │ │ │ │ │ ├── ArkLauncherTest.java │ │ │ │ │ ├── ClasspathLauncherTest.java │ │ │ │ │ └── MainMethodRunnerTest.java │ │ │ │ └── loader/ │ │ │ │ ├── DirectoryBizArchiveTest.java │ │ │ │ ├── DirectoryContainerArchiveTest.java │ │ │ │ ├── EmbedClassPathArchiveTest.java │ │ │ │ ├── ExecutableArkBizJarTest.java │ │ │ │ ├── ExplodedBizArchiveTest.java │ │ │ │ ├── archive/ │ │ │ │ │ ├── ExplodedArchiveTest.java │ │ │ │ │ └── JarFileArchiveTest.java │ │ │ │ ├── jar/ │ │ │ │ │ ├── HandlerTest.java │ │ │ │ │ ├── JarEntryTest.java │ │ │ │ │ ├── JarFileTest.java │ │ │ │ │ ├── JarURLConnectionTest.java │ │ │ │ │ ├── JarUtilsParseArtifactIdFromUnpackedDirTest.java │ │ │ │ │ ├── JarUtilsTest.java │ │ │ │ │ ├── JarUtilsTestHelper.java │ │ │ │ │ └── ZipInflaterInputStreamTest.java │ │ │ │ └── test/ │ │ │ │ ├── base/ │ │ │ │ │ └── BaseTest.java │ │ │ │ ├── data/ │ │ │ │ │ └── RandomAccessDataFileTest.java │ │ │ │ ├── jar/ │ │ │ │ │ ├── AsciiBytesTest.java │ │ │ │ │ ├── BytesTest.java │ │ │ │ │ ├── CentralDirectoryEndRecordTest.java │ │ │ │ │ ├── CentralDirectoryFileHeaderTest.java │ │ │ │ │ ├── CentralDirectoryParserTest.java │ │ │ │ │ ├── JarFileTest.java │ │ │ │ │ └── JarUtilsTest.java │ │ │ │ └── util/ │ │ │ │ └── ModifyPathUtilsTest.java │ │ │ └── resources/ │ │ │ ├── conf/ │ │ │ │ └── ark/ │ │ │ │ ├── bootstrap.properties │ │ │ │ └── log/ │ │ │ │ └── logback-conf.xml │ │ │ ├── empty-file │ │ │ ├── example-jarinjarinjar.jar │ │ │ ├── exploded-archive-test/ │ │ │ │ ├── META-INF/ │ │ │ │ │ └── MANIFEST.MF │ │ │ │ ├── example-jarinjarinjar.jar │ │ │ │ └── sample-biz.jar │ │ │ ├── junit-4.12.jar │ │ │ ├── pom-properties/ │ │ │ │ └── pom.properties │ │ │ ├── pom.xml │ │ │ ├── sample-biz-surefire.jar │ │ │ ├── sample-biz-withjar.jar │ │ │ ├── sample-biz.jar │ │ │ ├── sample-springboot-fat-biz.jar │ │ │ ├── static-combine-demo.jar │ │ │ └── xxxxx.jar-unpack/ │ │ │ └── pom.properties │ │ ├── container/ │ │ │ ├── CLAUDE.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── alipay/ │ │ │ │ │ └── sofa/ │ │ │ │ │ └── ark/ │ │ │ │ │ └── container/ │ │ │ │ │ ├── ArkContainer.java │ │ │ │ │ ├── guice/ │ │ │ │ │ │ └── ContainerModule.java │ │ │ │ │ ├── model/ │ │ │ │ │ │ ├── BizModel.java │ │ │ │ │ │ ├── PluginContextImpl.java │ │ │ │ │ │ └── PluginModel.java │ │ │ │ │ ├── pipeline/ │ │ │ │ │ │ ├── DeployBizStage.java │ │ │ │ │ │ ├── DeployPluginStage.java │ │ │ │ │ │ ├── ExtensionLoaderStage.java │ │ │ │ │ │ ├── FinishStartupStage.java │ │ │ │ │ │ ├── HandleArchiveStage.java │ │ │ │ │ │ ├── RegisterServiceStage.java │ │ │ │ │ │ └── StandardPipeline.java │ │ │ │ │ ├── registry/ │ │ │ │ │ │ ├── AbstractServiceProvider.java │ │ │ │ │ │ ├── ContainerServiceProvider.java │ │ │ │ │ │ ├── DefaultServiceFilter.java │ │ │ │ │ │ ├── PluginServiceProvider.java │ │ │ │ │ │ ├── ServiceMetadataImpl.java │ │ │ │ │ │ └── ServiceReferenceImpl.java │ │ │ │ │ ├── service/ │ │ │ │ │ │ ├── ArkServiceContainer.java │ │ │ │ │ │ ├── ArkServiceContainerHolder.java │ │ │ │ │ │ ├── biz/ │ │ │ │ │ │ │ ├── BizCommandProvider.java │ │ │ │ │ │ │ ├── BizDeployServiceImpl.java │ │ │ │ │ │ │ ├── BizFactoryServiceImpl.java │ │ │ │ │ │ │ ├── BizManagerServiceImpl.java │ │ │ │ │ │ │ └── DefaultBizDeployer.java │ │ │ │ │ │ ├── classloader/ │ │ │ │ │ │ │ ├── AbstractClasspathClassLoader.java │ │ │ │ │ │ │ ├── BizClassLoader.java │ │ │ │ │ │ │ ├── ClassLoaderServiceImpl.java │ │ │ │ │ │ │ ├── CompoundEnumeration.java │ │ │ │ │ │ │ ├── JDKDelegateClassLoader.java │ │ │ │ │ │ │ └── PluginClassLoader.java │ │ │ │ │ │ ├── event/ │ │ │ │ │ │ │ └── EventAdminServiceImpl.java │ │ │ │ │ │ ├── extension/ │ │ │ │ │ │ │ └── ExtensionLoaderServiceImpl.java │ │ │ │ │ │ ├── injection/ │ │ │ │ │ │ │ └── InjectionServiceImpl.java │ │ │ │ │ │ ├── plugin/ │ │ │ │ │ │ │ ├── PluginCommandProvider.java │ │ │ │ │ │ │ ├── PluginDeployServiceImpl.java │ │ │ │ │ │ │ ├── PluginFactoryServiceImpl.java │ │ │ │ │ │ │ └── PluginManagerServiceImpl.java │ │ │ │ │ │ ├── registry/ │ │ │ │ │ │ │ └── RegistryServiceImpl.java │ │ │ │ │ │ └── retrieval/ │ │ │ │ │ │ ├── ClassInfoMethod.java │ │ │ │ │ │ ├── ClassInfoVO.java │ │ │ │ │ │ ├── InfoQueryCommandProvider.java │ │ │ │ │ │ └── ViewRender.java │ │ │ │ │ ├── session/ │ │ │ │ │ │ ├── NettyTelnetServer.java │ │ │ │ │ │ ├── StandardTelnetServerImpl.java │ │ │ │ │ │ └── handler/ │ │ │ │ │ │ ├── AbstractTerminalTypeMapping.java │ │ │ │ │ │ └── ArkCommandHandler.java │ │ │ │ │ └── test/ │ │ │ │ │ ├── NoneDelegateTestClassLoader.java │ │ │ │ │ ├── TestClassLoader.java │ │ │ │ │ └── TestHelper.java │ │ │ │ └── resources/ │ │ │ │ └── META-INF/ │ │ │ │ └── services/ │ │ │ │ └── com.alipay.sofa.ark.common.guice.AbstractArkGuiceModule │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alipay/ │ │ │ │ └── sofa/ │ │ │ │ └── ark/ │ │ │ │ └── container/ │ │ │ │ ├── ArkContainerTest.java │ │ │ │ ├── BaseTest.java │ │ │ │ ├── ClassLoaderTest.java │ │ │ │ ├── model/ │ │ │ │ │ └── BizModelTest.java │ │ │ │ ├── pipeline/ │ │ │ │ │ └── HandleArchiveStageTest.java │ │ │ │ ├── service/ │ │ │ │ │ ├── ArkServiceContainerTest.java │ │ │ │ │ ├── api/ │ │ │ │ │ │ └── ArkClientTest.java │ │ │ │ │ ├── biz/ │ │ │ │ │ │ ├── BizCommandProviderTest.java │ │ │ │ │ │ ├── BizFactoryServiceTest.java │ │ │ │ │ │ ├── BizManagerServiceTest.java │ │ │ │ │ │ └── hook/ │ │ │ │ │ │ └── TestAddBizToStaticDeployHook.java │ │ │ │ │ ├── classloader/ │ │ │ │ │ │ ├── BizClassLoaderTest.java │ │ │ │ │ │ ├── ClassLoaderConcurrencyTest.java │ │ │ │ │ │ ├── ClassLoaderHookTest.java │ │ │ │ │ │ ├── ClassLoaderServiceTest.java │ │ │ │ │ │ ├── CompoundEnumerationTest.java │ │ │ │ │ │ ├── PluginClassLoaderTest.java │ │ │ │ │ │ └── hook/ │ │ │ │ │ │ ├── AbstractClassLoaderHook.java │ │ │ │ │ │ ├── TestBizClassLoaderHook.java │ │ │ │ │ │ ├── TestDefaultBizClassLoaderHook.java │ │ │ │ │ │ └── TestPluginClassLoaderHook.java │ │ │ │ │ ├── event/ │ │ │ │ │ │ ├── EventAdminServiceTest.java │ │ │ │ │ │ ├── EventTest.java │ │ │ │ │ │ ├── GlobalEventHandlerTest.java │ │ │ │ │ │ └── MultiEventTest.java │ │ │ │ │ ├── extension/ │ │ │ │ │ │ ├── ExtensionClassTest.java │ │ │ │ │ │ ├── ExtensionServiceTest.java │ │ │ │ │ │ └── spi/ │ │ │ │ │ │ ├── ServiceA.java │ │ │ │ │ │ ├── ServiceB.java │ │ │ │ │ │ ├── ServiceC.java │ │ │ │ │ │ ├── ServiceD.java │ │ │ │ │ │ ├── SingletonService.java │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ ├── ServiceBImpl1.java │ │ │ │ │ │ ├── ServiceBImpl2.java │ │ │ │ │ │ ├── ServiceBImpl3.java │ │ │ │ │ │ ├── ServiceBImpl4.java │ │ │ │ │ │ ├── ServiceDImpl.java │ │ │ │ │ │ └── SingletonServiceImpl.java │ │ │ │ │ ├── injection/ │ │ │ │ │ │ └── InjectionServiceTest.java │ │ │ │ │ ├── pipeline/ │ │ │ │ │ │ └── HandleArchiveTest.java │ │ │ │ │ ├── plugin/ │ │ │ │ │ │ ├── PluginCommandProviderTest.java │ │ │ │ │ │ ├── PluginFactoryServiceTest.java │ │ │ │ │ │ └── PluginManagerServiceTest.java │ │ │ │ │ ├── registry/ │ │ │ │ │ │ └── ServiceRegistrationTest.java │ │ │ │ │ ├── retrieval/ │ │ │ │ │ │ ├── ClassInfoMethodTest.java │ │ │ │ │ │ ├── InfoQueryCommandProviderTest.java │ │ │ │ │ │ └── ViewRenderTest.java │ │ │ │ │ └── session/ │ │ │ │ │ └── CommandHandlerTest.java │ │ │ │ ├── session/ │ │ │ │ │ └── NettyTelnetServerTest.java │ │ │ │ ├── test/ │ │ │ │ │ └── TestHelperTest.java │ │ │ │ └── testdata/ │ │ │ │ ├── ITest.java │ │ │ │ ├── activator/ │ │ │ │ │ ├── PluginActivatorA.java │ │ │ │ │ ├── PluginActivatorADup.java │ │ │ │ │ ├── PluginActivatorB.java │ │ │ │ │ └── PluginActivatorC.java │ │ │ │ ├── classloader/ │ │ │ │ │ └── ClassLoaderTestClass.java │ │ │ │ └── impl/ │ │ │ │ ├── TestObjectA.java │ │ │ │ ├── TestObjectB.java │ │ │ │ └── TestObjectC.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── services/ │ │ │ │ └── sofa-ark/ │ │ │ │ ├── com.alipay.sofa.ark.container.service.extension.spi.ServiceB │ │ │ │ ├── com.alipay.sofa.ark.spi.service.biz.AddBizToStaticDeployHook │ │ │ │ ├── com.alipay.sofa.ark.spi.service.classloader.ClassLoaderHook │ │ │ │ ├── sample-ark-2.0.1-ark-biz.jar │ │ │ │ ├── serviceA │ │ │ │ └── serviceD │ │ │ ├── aopalliance-1.0.jar │ │ │ ├── com.springsource.org.aopalliance-1.0.0.jar │ │ │ ├── export/ │ │ │ │ ├── folderA/ │ │ │ │ │ ├── test1.xml │ │ │ │ │ └── test2.xml │ │ │ │ └── folderB/ │ │ │ │ ├── test3.xml │ │ │ │ └── test4.xml │ │ │ ├── multi_export.xml │ │ │ ├── pluginA_export_resource1.xml │ │ │ ├── pluginA_export_resource2.xml │ │ │ ├── pluginA_not_export_resource.xml │ │ │ ├── profile-test.jar │ │ │ ├── sample-ark-1.0.0-ark-biz.jar │ │ │ ├── sample-ark-2.0.0-ark-biz.jar │ │ │ ├── sample-ark-3.0.0-ark-biz.jar │ │ │ ├── sample-ark-4.0.0-ark-biz.jar │ │ │ ├── sample-ark-5.0.0-ark-biz.jar │ │ │ ├── sample-ark-plugin-common-0.5.1.jar │ │ │ ├── sample-biz.jar │ │ │ ├── sample-plugin.jar │ │ │ ├── sofa-ark-sample-springboot-ark-0.3.0.jar │ │ │ ├── static-combine-springboot-executable.jar │ │ │ └── test.jar │ │ └── pom.xml │ ├── pom.xml │ └── support/ │ ├── ark-gradle-plugin/ │ │ ├── README.md │ │ ├── build.gradle │ │ ├── settings.gradle │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alipay/ │ │ └── sofa/ │ │ └── ark/ │ │ └── plugin/ │ │ ├── ArkArchiveSupport.java │ │ ├── ArkBizCopyAction.java │ │ ├── ArkJar.java │ │ ├── ArkPluginAction.java │ │ ├── BootArchive.java │ │ ├── DefaultTimeZoneOffset.java │ │ ├── JarTypeFileSpec.java │ │ ├── LoaderZipEntries.java │ │ ├── MainClassFinder.java │ │ ├── Nullable.java │ │ ├── ResolveMainClassName.java │ │ ├── SofaArkGradlePlugin.java │ │ ├── SofaArkGradlePluginExtension.java │ │ ├── StringUtils.java │ │ └── ZipCompression.java │ ├── ark-maven-plugin/ │ │ ├── CLAUDE.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alipay/ │ │ │ └── sofa/ │ │ │ └── ark/ │ │ │ └── boot/ │ │ │ └── mojo/ │ │ │ ├── ArtifactsLibraries.java │ │ │ ├── MavenUtils.java │ │ │ ├── ModuleSlimConfig.java │ │ │ ├── ModuleSlimExecutor.java │ │ │ ├── RepackageMojo.java │ │ │ ├── model/ │ │ │ │ └── ArkConfigHolder.java │ │ │ └── utils/ │ │ │ └── ParseUtils.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alipay/ │ │ │ └── sofa/ │ │ │ └── ark/ │ │ │ └── boot/ │ │ │ └── mojo/ │ │ │ ├── ArtifactsLibrariesTest.java │ │ │ ├── CommonUtils.java │ │ │ ├── MavenUtilsTest.java │ │ │ ├── ModuleSlimExecutorTest.java │ │ │ ├── ReflectionUtils.java │ │ │ ├── RepackageMojoTest.java │ │ │ └── utils/ │ │ │ └── ParseUtilsTest.java │ │ └── resources/ │ │ ├── baseDir/ │ │ │ └── conf/ │ │ │ └── ark/ │ │ │ ├── bootstrap.properties │ │ │ └── bootstrap.yml │ │ ├── dependency-tree-mock.txt │ │ ├── excludes.txt │ │ └── test-jar.jar │ ├── ark-plugin-gradle-plugin/ │ │ ├── README.md │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alipay/ │ │ │ └── sofa/ │ │ │ └── ark/ │ │ │ └── boot/ │ │ │ └── mojo/ │ │ │ ├── ArkPlugin.java │ │ │ ├── ArkPluginCopyAction.java │ │ │ ├── ArkPluginExtension.java │ │ │ ├── ArkPluginJarTask.java │ │ │ └── BaseConfig.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alipay/ │ │ └── sofa/ │ │ └── ark/ │ │ └── boot/ │ │ └── mojo/ │ │ └── ArkPluginExtensionTest.java │ ├── ark-plugin-maven-plugin/ │ │ ├── CLAUDE.md │ │ ├── META-INF/ │ │ │ └── MANIFEST.MF │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alipay/ │ │ │ │ └── sofa/ │ │ │ │ └── ark/ │ │ │ │ └── plugin/ │ │ │ │ └── mojo/ │ │ │ │ ├── AbstractPropertiesConfig.java │ │ │ │ ├── ArkPluginMojo.java │ │ │ │ ├── ExportConfig.java │ │ │ │ ├── ImportConfig.java │ │ │ │ ├── LinkedAttributes.java │ │ │ │ ├── LinkedManifest.java │ │ │ │ └── LinkedProperties.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── maven/ │ │ │ └── plugin.xml │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alipay/ │ │ │ └── sofa/ │ │ │ └── ark/ │ │ │ └── plugin/ │ │ │ └── mojo/ │ │ │ ├── ArkPluginMojoTest.java │ │ │ ├── LinkedAttributesTest.java │ │ │ ├── LinkedPropertiesTest.java │ │ │ └── test/ │ │ │ └── ArkPluginMojoTest.java │ │ └── resources/ │ │ ├── a-test-demo.jar │ │ └── test-demo.jar │ ├── ark-springboot-integration/ │ │ ├── ark-common-springboot/ │ │ │ ├── CLAUDE.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alipay/ │ │ │ └── sofa/ │ │ │ └── ark/ │ │ │ └── springboot/ │ │ │ └── condition/ │ │ │ ├── ConditionalOnArkEnabled.java │ │ │ ├── ConditionalOnSpringBootVersion.java │ │ │ ├── OnArkEnabled.java │ │ │ └── OnSpringBootVersion.java │ │ ├── ark-compatible-springboot1/ │ │ │ ├── CLAUDE.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── alipay/ │ │ │ │ │ └── sofa/ │ │ │ │ │ └── ark/ │ │ │ │ │ └── springboot1/ │ │ │ │ │ ├── CompatibleSpringBoot1AutoConfiguration.java │ │ │ │ │ ├── endpoint/ │ │ │ │ │ │ ├── IntrospectBizEndpoint.java │ │ │ │ │ │ └── IntrospectBizEndpointMvcAdapter.java │ │ │ │ │ └── web/ │ │ │ │ │ ├── ArkAutoConfiguration.java │ │ │ │ │ ├── ArkTomcatEmbeddedServletContainer.java │ │ │ │ │ ├── ArkTomcatEmbeddedServletContainerFactory.java │ │ │ │ │ └── SwitchClassLoaderFilter.java │ │ │ │ └── resources/ │ │ │ │ └── META-INF/ │ │ │ │ └── spring.factories │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alipay/ │ │ │ │ └── sofa/ │ │ │ │ └── ark/ │ │ │ │ ├── springboot1/ │ │ │ │ │ └── web/ │ │ │ │ │ ├── ArkTomcatEmbeddedServletContainerTest.java │ │ │ │ │ └── ArkTomcatServletWebServerFactoryTest.java │ │ │ │ └── test/ │ │ │ │ └── springboot1/ │ │ │ │ ├── IntrospectBizEndpointOnArkDisabledTest.java │ │ │ │ └── IntrospectBizEndpointOnArkEnabledTest.java │ │ │ └── resources/ │ │ │ └── logback.xml │ │ ├── ark-compatible-springboot2/ │ │ │ ├── CLAUDE.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── alipay/ │ │ │ │ │ └── sofa/ │ │ │ │ │ └── ark/ │ │ │ │ │ └── springboot2/ │ │ │ │ │ ├── CompatibleSpringBoot2AutoConfiguration.java │ │ │ │ │ ├── endpoint/ │ │ │ │ │ │ └── IntrospectBizEndpoint.java │ │ │ │ │ └── web/ │ │ │ │ │ └── SwitchClassLoaderFilter.java │ │ │ │ └── resources/ │ │ │ │ └── META-INF/ │ │ │ │ └── spring.factories │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alipay/ │ │ │ │ └── sofa/ │ │ │ │ └── ark/ │ │ │ │ └── test/ │ │ │ │ └── springboot2/ │ │ │ │ ├── SpringBoot2IntrospectBizEndpointOnArkDisabledTest.java │ │ │ │ └── SpringBoot2IntrospectBizEndpointOnArkEnabledTest.java │ │ │ └── resources/ │ │ │ └── logback.xml │ │ └── ark-springboot-starter/ │ │ ├── CLAUDE.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alipay/ │ │ │ │ └── sofa/ │ │ │ │ └── ark/ │ │ │ │ └── springboot/ │ │ │ │ ├── ArkAutoProcessorConfiguration.java │ │ │ │ ├── ArkReactiveAutoConfiguration.java │ │ │ │ ├── ArkServletAutoConfiguration.java │ │ │ │ ├── ArkServletLegacyAutoConfiguration.java │ │ │ │ ├── listener/ │ │ │ │ │ ├── ArkApplicationStartListener.java │ │ │ │ │ ├── ArkDeployStaticBizListener.java │ │ │ │ │ └── PropertiesResetListener.java │ │ │ │ ├── loader/ │ │ │ │ │ ├── CachedLaunchedURLClassLoader.java │ │ │ │ │ └── JarLauncher.java │ │ │ │ ├── processor/ │ │ │ │ │ ├── ArkEventHandlerProcessor.java │ │ │ │ │ └── ArkServiceInjectProcessor.java │ │ │ │ ├── runner/ │ │ │ │ │ ├── ArkBootEmbedRunner.java │ │ │ │ │ └── ArkBootRunner.java │ │ │ │ └── web/ │ │ │ │ ├── ArkCompositeReactorHttpHandlerAdapter.java │ │ │ │ ├── ArkNettyReactiveWebServerFactory.java │ │ │ │ ├── ArkNettyWebServer.java │ │ │ │ ├── ArkTomcatServletWebServerFactory.java │ │ │ │ └── ArkTomcatWebServer.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alipay/ │ │ │ └── sofa/ │ │ │ └── ark/ │ │ │ ├── springboot/ │ │ │ │ ├── listener/ │ │ │ │ │ └── ArkDeployStaticBizListenerTest.java │ │ │ │ ├── loader/ │ │ │ │ │ └── CachedLaunchedURLClassLoaderTest.java │ │ │ │ └── web/ │ │ │ │ ├── ArkTomcatServletWebServerFactoryTest.java │ │ │ │ └── ArkTomcatWebServerTest.java │ │ │ └── test/ │ │ │ ├── ArkBootRunnerTest.java │ │ │ ├── ArkBootTestNGTest.java │ │ │ ├── MultiArkBootRunnerTest.java │ │ │ ├── SpringbootRunnerTest.java │ │ │ └── springboot/ │ │ │ ├── BaseSpringApplication.java │ │ │ ├── RegisterMockEmbedTomcatService.java │ │ │ ├── TestValueHolder.java │ │ │ ├── facade/ │ │ │ │ └── SampleService.java │ │ │ └── impl/ │ │ │ ├── SampleServiceImpl.java │ │ │ └── TestBizEventHandler.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── sofa-ark-test/ │ │ │ └── sofa-ark-test.xml │ │ ├── config/ │ │ │ └── application.properties │ │ ├── logback.xml │ │ └── sample-biz-0.3.0.jar │ ├── ark-support-starter/ │ │ ├── CLAUDE.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alipay/ │ │ │ │ └── sofa/ │ │ │ │ └── ark/ │ │ │ │ └── support/ │ │ │ │ ├── common/ │ │ │ │ │ ├── AddBizInResourcesHook.java │ │ │ │ │ ├── DelegateArkContainer.java │ │ │ │ │ ├── DelegateToMasterBizClassLoaderHook.java │ │ │ │ │ └── MasterBizEnvironmentHolder.java │ │ │ │ ├── listener/ │ │ │ │ │ ├── ArkTestNGAlterSuiteListener.java │ │ │ │ │ ├── ArkTestNGExecutionListener.java │ │ │ │ │ ├── ArkTestNGInvokedMethodListener.java │ │ │ │ │ ├── TestNGOnArk.java │ │ │ │ │ └── TestNGOnArkEmbeded.java │ │ │ │ ├── runner/ │ │ │ │ │ ├── ArkJUnit4EmbedRunner.java │ │ │ │ │ ├── ArkJUnit4Runner.java │ │ │ │ │ └── JUnitExecutionListener.java │ │ │ │ ├── startup/ │ │ │ │ │ ├── EmbedSofaArkBootstrap.java │ │ │ │ │ ├── EntryMethod.java │ │ │ │ │ └── SofaArkBootstrap.java │ │ │ │ └── thread/ │ │ │ │ ├── IsolatedThreadGroup.java │ │ │ │ └── LaunchRunner.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ ├── org.testng.ITestNGListener │ │ │ └── sofa-ark/ │ │ │ └── com.alipay.sofa.ark.spi.service.biz.AddBizToStaticDeployHook │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alipay/ │ │ │ └── sofa/ │ │ │ └── ark/ │ │ │ └── support/ │ │ │ ├── AddBizInResourcesHookTest.java │ │ │ ├── DefaultClassLoaderHookTest.java │ │ │ ├── MultiSuiteTest.java │ │ │ ├── TestNGCommonTest.java │ │ │ ├── TestNGOnArkTest.java │ │ │ ├── runner/ │ │ │ │ ├── ArkJUnit4RunnerTest.java │ │ │ │ └── CommonJUnit4Test.java │ │ │ ├── startup/ │ │ │ │ └── EmbedSofaArkBootstrapTest.java │ │ │ └── thread/ │ │ │ ├── IsolatedThreadGroupTest.java │ │ │ └── LaunchRunnerTest.java │ │ └── resources/ │ │ ├── SOFA-ARK/ │ │ │ └── biz/ │ │ │ └── biz1-bootstrap-0.0.1-SNAPSHOT-ark-biz.jar │ │ ├── aopalliance-1.0.jar │ │ ├── com.springsource.org.aopalliance-1.0.0.jar │ │ ├── sample-ark-1.0.0-ark-biz.jar │ │ ├── sample-ark-plugin-common-0.5.1.jar │ │ └── sofa-ark-sample-springboot-ark-0.3.0.jar │ ├── ark-tools/ │ │ ├── CLAUDE.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alipay/ │ │ │ └── sofa/ │ │ │ └── ark/ │ │ │ └── tools/ │ │ │ ├── ArtifactItem.java │ │ │ ├── JarWriter.java │ │ │ ├── Layout.java │ │ │ ├── Layouts.java │ │ │ ├── Libraries.java │ │ │ ├── Library.java │ │ │ ├── LibraryCallback.java │ │ │ ├── LibraryScope.java │ │ │ ├── LoaderClassesWriter.java │ │ │ ├── MainClassFinder.java │ │ │ ├── Repackager.java │ │ │ ├── RepackagingLayout.java │ │ │ └── git/ │ │ │ ├── GitInfo.java │ │ │ └── JGitParser.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alipay/ │ │ │ └── sofa/ │ │ │ └── ark/ │ │ │ └── tools/ │ │ │ ├── ArtifactItemTest.java │ │ │ ├── JarWriterTest.java │ │ │ ├── LayoutsTest.java │ │ │ ├── MainClassFinderTest.java │ │ │ ├── RepackagerTest.java │ │ │ └── git/ │ │ │ └── JGitParserTest.java │ │ └── resources/ │ │ ├── conf/ │ │ │ └── test-jar.jar │ │ ├── test-jar.jar │ │ └── test-pom.xml │ └── pom.xml └── sofa-ark-plugin/ ├── config-ark-plugin/ │ ├── CLAUDE.md │ ├── DEMO_SHOW.md │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── alipay/ │ │ └── sofa/ │ │ └── ark/ │ │ └── config/ │ │ ├── ConfigBaseActivator.java │ │ ├── ConfigProcessor.java │ │ ├── ConfigTypeEnum.java │ │ ├── LazyActivatorWrapper.java │ │ ├── OperationProcessor.java │ │ ├── RegistryConfig.java │ │ ├── apollo/ │ │ │ └── ApolloConfigActivator.java │ │ ├── util/ │ │ │ ├── NetUtils.java │ │ │ └── OperationTransformer.java │ │ └── zk/ │ │ ├── ZookeeperConfigActivator.java │ │ └── ZookeeperConfigurator.java │ └── test/ │ └── java/ │ └── com/ │ └── alipay/ │ └── sofa/ │ └── ark/ │ └── config/ │ ├── ApolloConfigActivatorTest.java │ ├── ConfigBaseActivatorTest.java │ ├── MockApolloConfig.java │ ├── OperationTransformerTest.java │ └── ZookeeperConfiguratorTest.java ├── netty-ark-plugin/ │ ├── CLAUDE.md │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── alipay/ │ └── sofa/ │ └── ark/ │ ├── NettyPluginActivator.java │ └── netty/ │ ├── ArkNettyIdentification.java │ └── EmbeddedServerServiceImpl.java ├── pom.xml └── web-ark-plugin/ ├── CLAUDE.md ├── pom.xml └── src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── alipay/ │ │ └── sofa/ │ │ └── ark/ │ │ └── web/ │ │ └── embed/ │ │ ├── WebPluginActivator.java │ │ └── tomcat/ │ │ ├── ArkTomcatEmbeddedWebappClassLoader.java │ │ ├── EmbeddedServerServiceImpl.java │ │ └── SwitchClassLoaderAutoConfiguration.java │ └── resources/ │ └── META-INF/ │ ├── spring/ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ └── spring.factories └── test/ └── java/ └── com/ └── alipay/ └── sofa/ └── ark/ └── web/ └── embed/ ├── WebPluginActivatorTest.java └── tomcat/ ├── ArkTomcatEmbeddedWebappClassLoaderTest.java └── EmbeddedServerServiceImplTest.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature Request about: Propose a feature on improving user experience or bringing a new functionality. --- ### Feature description Describe the feature details with Chinese or English. ### Additional notes Add other notes if necessary. ================================================ FILE: .github/ISSUE_TEMPLATE/question_or_bug_report.md ================================================ --- name: Question or Bug Report about: Create a question or bug report to help us improve. --- ### Describe the question or bug A clear and concise description of what the question or bug is. ### Expected behavior A clear and concise description of what you expected to happen. ### Actual behavior A clear and concise description of what actually happened. ### Steps to reproduce Steps to reproduce the problem: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error ### Screenshots If applicable, add screenshots to help explain your problem. ### Minimal yet complete reproducer code (or GitHub URL to code) ### Environment - SOFAArk version: - JVM version (e.g. `java -version`): - OS version (e.g. `uname -a`): - Maven version: - IDE version: ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ### Motivation Explain the context, and why you're making that change. To make others understand what is the problem you're trying to solve. ### Modification Describe the idea and modifications you've done. ### Result Resolved or fixed #. If there is no issue then describe the changes introduced by this PR. ================================================ FILE: .github/workflows/cloud_code_scan.yml ================================================ name: Alipay Cloud Devops Codescan on: pull_request_target: jobs: stc: # Code security scanning runs-on: ubuntu-latest steps: - name: codeScan uses: layotto/alipay-cloud-devops-codescan@main with: parent_uid: ${{ secrets.ALI_PID }} private_key: ${{ secrets.ALI_PK }} scan_type: stc sca: # Open source compliance scanning runs-on: ubuntu-latest steps: - name: codeScan uses: layotto/alipay-cloud-devops-codescan@main with: parent_uid: ${{ secrets.ALI_PID }} private_key: ${{ secrets.ALI_PK }} scan_type: sca ================================================ FILE: .github/workflows/inactive_issues_robot.yml ================================================ name: stale issues monitor and close 🌊 on: schedule: - cron: '30 1 * * *' jobs: stale: name: "Update Stale Status" runs-on: ubuntu-latest steps: - uses: actions/stale@v4 with: days-before-stale: 30 days-before-close: 7 stale-issue-message: > This issue has been automatically marked as stale because it has not had recent activity in the last 30 days. It will be closed in the next 7 days unless it is tagged (pinned, good first issue or help wanted) or other activity occurs. Thank you for your contributions. close-issue-message: > This issue has been automatically closed because it has not had activity in the last 37 days. If this issue is still valid, please ping a maintainer and ask them to label it as pinned, good first issue or help wanted. Thank you for your contributions. stale-pr-message: > This pull request has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed in 7 days if no further activity occurs. Please feel free to give a status update now, ping for review, or re-open when it's ready. Thank you for your contributions! close-pr-message: > This pull request has been automatically closed because it has not had activity in the last 37 days. Please feel free to give a status update now, ping for review, or re-open when it's ready. Thank you for your contributions! stale-issue-label: 'stale' exempt-issue-labels: 'pinned,good first issue,help wanted,backlog' stale-pr-label: 'stale' exempt-pr-labels: 'pinned' ================================================ FILE: .github/workflows/linux_unit_test.yml ================================================ # This workflow will build a Java project with Maven # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven name: Test for linux on: push: branches: - 'master' pull_request: branches: - 'master' workflow_dispatch: inputs: branch: description: 'Branch name to run tests on' required: false default: 'master' type: string jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: ref: ${{ github.event.inputs.branch || github.ref }} - name: Set up JDK 8 uses: actions/setup-java@v1 with: java-version: '8' distribution: 'adopt-hotspot' cache: 'maven' - name: Test with Maven run: mvn clean install -DskipTests -Dmaven.javadoc.skip=true -B -U && sh ./check_format.sh && mvn test - name: Codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} ================================================ FILE: .github/workflows/release.yml ================================================ # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven name: Release ## https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#release ## trigger manually on: workflow_dispatch: jobs: # build: # runs-on: ubuntu-latest # # steps: # - uses: actions/checkout@v3 # - name: Set up JDK 8 # uses: actions/setup-java@v3 # with: # java-version: '8' # distribution: 'temurin' # cache: maven # - name: Build with Maven # run: mvn clean install -DskipTests -B -U -e && sh ./check_format.sh release_for_jdk8: # needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 8 uses: actions/setup-java@v3 with: java-version: '8' distribution: 'temurin' cache: maven server-id: ossrh server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase - name: Build with Maven run: mvn --batch-mode deploy -DskipTests -Prelease env: MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} ================================================ FILE: .github/workflows/snapshot.yml ================================================ # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven name: Release SNAPSHOT ## https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#release ## trigger manually on: workflow_dispatch: jobs: # build: # runs-on: ubuntu-latest # # steps: # - uses: actions/checkout@v3 # - name: Set up JDK 8 # uses: actions/setup-java@v3 # with: # java-version: '8' # distribution: 'temurin' # cache: maven # - name: Build with Maven # run: mvn clean install -DskipTests -B -U -e && sh ./check_format.sh snapshot_for_jdk8: # needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 8 uses: actions/setup-java@v3 with: java-version: '8' distribution: 'temurin' cache: maven server-id: ossrh server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase - name: Build with Maven run: mvn --batch-mode deploy -DskipTests -Pdefault,snapshot env: MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} ================================================ FILE: .github/workflows/windows_unit_test.yml ================================================ # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. name: Test for windows on: push: branches: - 'master' pull_request: branches: - 'master' workflow_dispatch: inputs: branch: description: 'Branch name to run tests on' required: false default: 'master' type: string jobs: build: runs-on: windows-latest strategy: fail-fast: true steps: - uses: actions/checkout@v3 with: ref: ${{ github.event.inputs.branch || github.ref }} - name: Set up JDK 8 uses: actions/setup-java@v3 with: java-version: '8' distribution: 'adopt-hotspot' cache: maven - name: Build with Maven run: mvn clean install --% -DskipTests -Dmaven.javadoc.skip=true -B -U && sh ./check_format.sh && mvn test - name: Codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} ================================================ FILE: .gitignore ================================================ target/ *.iml .idea/ pom.xml.bak .DS_Store .settings .classpath *.log logs/ .flattened-pom.xml lib/ sofa-ark-parent/support/ark-plugin-maven-plugin/com/alipay/sofa/ark/plugin/mark sofa-ark-parent/support/ark-plugin-maven-plugin/null.ark.plugin sofa-ark-parent/support/ark-plugin-maven-plugin/null.ark.plugin.bak sofa-ark-parent/support/ark-plugin-maven-plugin/xxx.ark.plugin sofa-ark-parent/core/common/C/\temp dir\b\c/test.txt sofa-ark-parent/core/common/C:\\temp dir\\b\\c/test.txt .gradle/ build/ gradle/ gradlew gradlew.bat .claude/ ./change.md ================================================ FILE: .travis.yml ================================================ language: java sudo: false jdk: - openjdk8 install: - mvn clean install -DskipTests -B -U - mvn clean test script: - sh ./check_format.sh after_success: - bash <(curl -s https://codecov.io/bash) ================================================ FILE: CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## 0. 交互协议 - **交互语言**:工具与模型交互强制使用 **English**;用户输出强制使用 **中文** - **多轮对话**:如果工具返回的有可持续对话字段(如 `SESSION_ID`),记录该字段,并在后续调用中**强制思考**是否继续对话。Codex/Gemini 有时会因工具调用中断会话,若未得到需要的回复,应继续对话 - **沙箱安全**:除非用户明确允许,严禁 Codex/Gemini 对文件系统进行写操作。所有代码获取必须请求 `unified diff patch` 格式 - **渐进迭代**:多轮沟通和小步提交,持续改进 - 尊重事实比尊重我更为重要。如果我犯错,请毫不犹豫地指正我 ## 1. 代码主权与判断依据 - **代码主权**:外部模型生成的代码仅作为逻辑参考(Prototype),最终交付代码**必须经过分析思考和重构**,确保无冗余、企业生产级标准 - **判断依据**:始终以项目代码的搜索结果作为判断依据,严禁使用一般知识进行猜测,允许向用户表明不确定性。调用非内置库时,必须先用工具搜索外部知识,以搜索结果为依据编码 - **深度分析**:用第一性原理分析问题,深入思考本质 - **仅做针对性改动**:严禁影响项目现有的其他功能 - **持续进化**:用户在交互中明确纠正的问题和注意事项需同步追加在本文件的**项目专属约束**中 ## 2. 工具使用规范 执行任务前优先查找**内置工具、MCP 工具和 Skills** 中有没有可用的,判断Skills选择使用 using-superpowers 技能 - 先检索再编码,避免重复造轮子 - 文件修改:使用替换工具做精准替换 - 并行操作:无依赖步骤应使用 subagent 并行执行 ### 代码检索黄金规则 按场景选择最优工具: **第一层:语义搜索(ck)— "我不知道叫什么,但我知道它做什么"** - 适用:模糊概念搜索、不确定关键词时的探索 - 用法:`ck --sem "concept"` / `ck --hybrid "query"` / `ck --lex "keyword"` - 详细参考 [ck命令说明](ck-semantic-search.md) **第二层:符号搜索(Serena LSP)— "我知道符号名,要精确定位和操作"** - 适用:查找符号定义/引用、理解文件结构、符号级编辑和重命名 - 核心工具:`find_symbol`(定位)、`find_referencing_symbols`(引用链)、`get_symbols_overview`(结构) - 编辑工具:`replace_symbol_body`、`insert_after_symbol`、`rename_symbol` - 优势:Token 高效(按需加载符号体,不必读整文件) **第三层:文本搜索(Grep/Glob)— "我知道确切的文本模式"** - 适用:精确关键词/正则匹配、文件名模式查找、非代码文件搜索(yaml/json/env等) - 核心工具:`Grep`(内容正则)、`Glob`(文件名匹配) **第四层:直接读取(Read/WebFetch)— "我知道确切的位置,要看完整内容"** - 适用:已知文件路径读取完整内容、查看图片/PDF、获取网页信息 - 核心工具:`Read`(本地文件/图片/PDF/Notebook)、`WebFetch`(网页内容提取) - 注意:优先用前三层定位目标,再用 Read 读取;避免盲目读取大文件 ### 外部知识获取 遇到代码库以外不熟悉的知识,必须使用工具联网搜索,严禁猜测: - 通用搜索:`WebSearch` 或 `mcp__exa__web_search_exa` - 库文档:`mcp__context7__resolve-library-id` → `mcp__context7__get-library-docs` - 开源项目:优先使用 `mcp__mcp-deepwiki__deepwiki_fetch`,而非通用搜索工具 ## 3. 代码风格 - KISS — 能简单就不复杂 - DRY — 零容忍重复,必须复用 - 保护调用链 — 修改函数签名时同步更新所有调用点 - 文件大小:1000 行上限,超出按职责拆分 - 函数长度:100 行上限(不含空行),超出立即提取辅助函数 - Uses `Formatter.xml` for automatic code formatting during build - Apache 2.0 license header required on all Java files - Run `mvn clean install` before pushing to ensure formatting is applied ### 完成后清理 - 删除:临时文件、注释掉的废弃代码、未使用的导入、调试日志 ### 代码红线 - 除非用户明确说明,禁止破坏或改变现有功能 - 禁止对错误方案妥协 - 切勿将密钥、API 密钥或凭据硬编码到源代码中,使用环境变量 ## 4. 其他约束 - 添加新功能时**遵循模块化模式**,准确识别功能归属的分层 - 修改后**测试模块化服务器**,确保所有导入正常工作 - 重要链路必须有清晰的异常处理 ## 5. Git 规范 - 不主动提交,除非用户明确要求 - 不主动 push,除非用户明确要求 - Commit 格式:`(): ` - 提交前:`git diff` 确认改动范围 - 禁止 `--force` 推送到 main/master ## 6. Project Overview SOFAArk is a lightweight Java-based classloader-isolated framework open-sourced by Ant Financial. It provides: - **Class isolation**: Solve package dependency conflicts (e.g., using protobuf2 and protobuf3 simultaneously) - **Dynamic hot deployment**: Install/uninstall business modules at runtime - **Merged deployment**: Multiple applications can be packaged and run together ## 7. Build Commands ```bash # Full build (skip tests and javadoc) mvn clean install -DskipTests -Dmaven.javadoc.skip=true -B -U # Run all tests mvn test # Run tests for a specific module mvn test -pl sofa-ark-parent/core-impl/container # Run a single test class mvn test -Dtest=ArkContainerTest -pl sofa-ark-parent/core-impl/container # Format check (must run after build with no uncommitted files) sh ./check_format.sh # Release build mvn clean install -DskipTests -Dmaven.javadoc.skip=true -B -U -Prelease ``` ## 8. Project Structure ``` sofa-ark/ ├── sofa-ark-bom/ # Dependency management (versions) ├── sofa-ark-parent/ │ ├── core/ # Core interfaces and common code │ │ ├── api/ # Public API (ArkClient, ArkConfigs) │ │ ├── spi/ # Service Provider Interfaces │ │ ├── common/ # Shared utilities │ │ └── exception/ # Exception definitions │ ├── core-impl/ # Core implementations │ │ ├── container/ # Ark Container (runtime management) │ │ └── archive/ # Archive loading (JAR/directory handling) │ ├── support/ # Build tools and integrations │ │ ├── ark-maven-plugin/ # Maven plugin for building Ark packages │ │ ├── ark-plugin-maven-plugin/ # Maven plugin for building Ark Plugins │ │ ├── ark-springboot-integration/ # Spring Boot integration │ │ └── ark-tools/ # Repackaging utilities │ └── assembly/ # sofa-ark-all aggregated JAR └── sofa-ark-plugin/ # Built-in plugins (config, web, netty) ``` ## 9. Core Architecture ### Three Key Concepts 1. **Ark Container**: Runtime container that manages plugins and business modules. Entry point: `ArkContainer.main()` 2. **Ark Plugin**: Class-isolated plugin units. Loaded by `PluginClassLoader`. Plugins can import/export classes to share or isolate dependencies. 3. **Ark Biz**: Business modules loaded by `BizClassLoader`. Each biz has independent classloader isolation. ### Startup Pipeline The container executes these stages in order (see `StandardPipeline.java`): 1. `HandleArchiveStage` - Parse and resolve Ark archives 2. `RegisterServiceStage` - Register core services 3. `ExtensionLoaderStage` - Load SPI extensions 4. `DeployPluginStage` - Start all Ark Plugins 5. `DeployBizStage` - Start all Ark Biz modules 6. `FinishStartupStage` - Complete startup ### ClassLoader Hierarchy ``` Bootstrap ClassLoader ↓ Ark Container ClassLoader (loads sofa-ark-all) ↓ PluginClassLoader (one per plugin, bidirectional delegation between plugins) ↓ BizClassLoader (one per biz, can delegate to plugins) ``` ### Key APIs - `ArkClient` - Main API for installing/uninstalling/switching biz modules at runtime - `ArkConfigs` - Configuration management - `BizManagerService` - Manage business modules - `PluginManagerService` - Manage plugins ## 10. Maven Plugins ### sofa-ark-maven-plugin Builds executable Ark packages with `mvn package`: ```xml com.alipay.sofa sofa-ark-maven-plugin repackage ``` Key configurations: - `bizName` / `bizVersion` - Module identity - `excludes` / `excludeGroupIds` - Dependencies to exclude from the package - `denyImportPackages` - Packages the biz cannot import from plugins - `declaredMode` - Filter dependencies against declared list ### sofa-ark-plugin-maven-plugin Builds Ark Plugin packages: ```xml com.alipay.sofa sofa-ark-plugin-maven-plugin com.example.MyPluginActivator ``` ## 11. Versioning - Current version: 2.3.2 - Three-digit versioning: `major.minor.patch` - First digit: Breaking compatibility changes - Second digit: New features/enhancements - Third digit: Bug fixes ## 12. Running Specific Tests ### Run tests for a specific module ```bash # Run all tests in a module mvn test -pl sofa-ark-parent/core-impl/container # Run all tests in multiple modules mvn test -pl sofa-ark-parent/core-impl/container,sofa-ark-parent/core/api ``` ### Run a single test class ```bash # Run a specific test class mvn test -Dtest=ArkContainerTest -pl sofa-ark-parent/core-impl/container # Run a test class with pattern matching mvn test -Dtest=*ClassLoaderTest -pl sofa-ark-parent/core-impl/container ``` ### Run a single test method ```bash # Run a specific test method mvn test -Dtest=ArkContainerTest#testStart -pl sofa-ark-parent/core-impl/container ``` ### Run tests with specific groups (TestNG) ```bash # Run tests belonging to a specific group mvn test -Dgroups=unit -pl sofa-ark-parent/core-impl/container ``` ### Debug tests ```bash # Run tests with remote debugging (listen on port 5005) mvn test -Dmaven.surefire.debug -pl sofa-ark-parent/core-impl/container ``` ### Skip specific tests ```bash # Skip a specific test class mvn test -Dtest=!ArkContainerTest -pl sofa-ark-parent/core-impl/container # Skip tests matching a pattern mvn test -Dtest=!*.IntegrationTest -pl sofa-ark-parent/core-impl/container ``` ## 13. Troubleshooting Guide ### Build Issues #### 1. Compilation errors after pulling changes **Symptoms:** Compilation fails with "cannot find symbol" or similar errors. **Solutions:** ```bash # Clean and rebuild mvn clean install -DskipTests -Dmaven.javadoc.skip=true -B -U ``` #### 2. Code format check fails **Symptoms:** `check_format.sh` reports formatting issues. **Solutions:** ```bash # Ensure build is complete first (formatter runs during build) mvn clean install -DskipTests # Then run format check sh ./check_format.sh # If still failing, check for uncommitted files git status ``` #### 3. Dependency resolution failures **Symptoms:** Maven cannot resolve dependencies. **Solutions:** ```bash # Force update of snapshots and releases mvn clean install -U -DskipTests # Check local Maven repository for corrupted artifacts rm -rf ~/.m2/repository/com/alipay/sofa mvn clean install -DskipTests ``` ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hting1@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ ## Contributing to SOFAArk SOFAArk is released under the Apache 2.0 license, and follows a very standard Github development process, using Github tracker for issues and merging pull requests into master . If you would like to contribute something, or simply want to hack on the code this document should help you get started. ### Sign the Contributor License Agreement Before we accept a non-trivial patch or pull request we will need you to sign the Contributor License Agreement. Signing the contributor’s agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. Active contributors might be asked to join the core team, and given the ability to merge pull requests. ### Code Conventions None of these is essential for a pull request, but they will all help. 1. we provided a [code formatter file](./Formatter.xml), it will formatting automatically your project when during process of building. We would check code format when run ci test, so please ensure that you have built project before you push branch. 2. Make sure all new `.java` files to have a simple Javadoc class comment with at least an `@author` tag identifying you, and preferably at least a paragraph on what the class is for. 3. Add the ASF license header comment to all new `.java` files (copy from existing files in the project) 4. Add yourself as an `@author` to the `.java` files that you modify substantially (more than cosmetic changes). 5. Add some Javadocs. 6. A few unit tests would help a lot as well — someone has to do it. 7. When writing a commit message please follow [these conventions](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), if you are fixing an existing issue please add Fixes gh-XXXX at the end of the commit message (where XXXX is the issue number). 8. Ensure that code coverage does not decrease。 9. Contribute a PR as the rule of Gitflow Workflow; SOFAArk's version contains three digit, the first one is for compatibility; the second one is for new features and enhancement; the last one is for bug fix. ================================================ FILE: Formatter.xml ================================================ ================================================ FILE: HEADER ================================================ Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # SOFAArk Project [![Build Status](https://travis-ci.org/sofastack/sofa-ark.svg?branch=master)](https://travis-ci.org/sofastack/sofa-ark) [![Coverage Status](https://codecov.io/gh/sofastack/sofa-ark/branch/master/graph/badge.svg)](https://codecov.io/gh/sofastack/sofa-ark/branch/master/graph/badge.svg) [![Gitter](https://img.shields.io/badge/chat-on%20gitter-orange.svg)](https://gitter.im/sofa-ark/Lobby) ![license](https://img.shields.io/badge/license-Apache--2.0-green.svg) ![maven](https://img.shields.io/nexus/r/https/oss.sonatype.org/com.alipay.sofa/sofa-ark-all.svg) SOFAArk 作为类隔离组件,在蚂蚁内部演进出了一套完整的模块化应用研发框架和平台能力,欢迎大家移步:https://github.com/koupleless/koupleless ,一起讨论和建设模块化架构能力,一起探索微服务的下一站。 SOFAArk 是一款基于 Java 实现的动态热部署和轻量级类隔离框架,由蚂蚁集团开源贡献,主要提供应用模块的动态热部署和类隔离能力。基于 [Fat Jar](https://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html#executable-jar-jar-file-structure) 技术,可以将多个应用模块打包成一个自包含可运行的 Fat Jar,应用既可以是简单的单模块 Java 应用也可以是 SpringBoot/SOFABoot 应用。[访问网址](https://www.sofastack.tech/sofa-boot/docs/sofa-ark-readme?lang=zh-cn)进入快速开始并获取更多详细信息。 ## 背景 SOFAArk 最初的场景是解决 Java 开发常常会遇到的包依赖冲突的问题,尤其当工程应用变得臃肿庞大,包冲突的问题也会变得更加棘手,导致各种各样的报错,例如`LinkageError`, `NoSuchMethodError`等。实际开发中,可以采用多种方法来解决包冲突问题,比较常见的是类似 SpringBoot 的做法,统一管理应用所有依赖包的版本,保证这些三方包不存在依赖冲突。这种做法只能有效避免包冲突的问题,不能根本上解决包冲突的问题。如果某个应用的确需要在运行时使用两个相互冲突的包,例如 `protobuf2` 和 `protobuf3`,那么类似 SpringBoot 的做法依然解决不了问题。 为了彻底解决包冲突的问题,我们需要借助类隔离机制,使用不同的 ClassLoader 加载不同版本的三方依赖,进而隔离包冲突问题。OSGI 作为业内最出名的类隔离框架,自然是可以被用于解决上述包冲突问题,但是 OSGI 框架太过臃肿,功能繁杂。为了解决包冲突问题,引入 OSGI 框架,有牛刀杀鸡之嫌,反而使工程变得更加复杂,不利于开发。 SOFAArk 则采用较为轻量级的类隔离方案来解决日常经常遇到的包冲突问题,在蚂蚁金服内部服务于整个 [SOFABoot](https://github.com/sofastack/sofa-boot) 技术体系,弥补 SpringBoot 没有的类隔离能力。实际上,SOFAArk 是一个通用的轻量级类隔离框架,并不限于 SpringBoot 应用,也可以和其他的 Java 开发框架集成。 基于类隔离能力 SOFAArk 还提供了动态热部署能力。SOFAArk 不但支持将多个应用合并打成一个可执行的 Fat Jar 包,也支持运行时通过 API 或者 Zookeeper 动态推送配置达到动态部署应用模块的能力。在多团队协作开发时,各个功能模块由不同的团队负责开发,通常情况下,这些功能模块独立开发,但是运行时部署在一起。借助 SOFAArk 提供的合并部署能力,各团队开发时拥有相当大自由度,只需要定义各模块之间的交互接口即可,尤其对于中台应用开发,提高团队合作效率。除了合并部署,SOFAArk 还对接了 Zookeeper 接受动态配置,控制应用模块的动态安装和卸载。 ## 原理 SOFAArk 框架包含有三个概念,`Ark Container`, `Ark Plugin` 和 `Ark Biz`; 运行时逻辑结构图如下: ![framework](resource/SOFA-Ark-Framework.png) 在介绍这三个概念之前,为了统一术语,有必要先说一下所谓的 `Ark 包`;Ark 包是满足特定目录格式要求的 `Executed Fat Jar`,使用官方提供的 `Maven` 插件 `sofa-ark-maven-plugin`可以将工程应用打包成一个标准格式的 `Ark 包`;使用命令 `java -jar application.jar`即可在 Ark 容器之上启动应用;`Ark 包` 通常包含 `Ark Container`、`Ark Plugin`、 `Ark Biz`;以下我们针对这三个概念简单做下名词解释: + `Ark Container`: Ark 容器,负责整个运行时的管理;`Ark Plugin` 和 `Ark Biz` 运行在 Ark 容器之上;容器具备管理多插件、多应用的功能;容器启动成功后,会自动解析 classpath 包含的 `Ark Plugin` 和 `Ark Biz` 依赖,完成隔离加载并按优先级依次启动之; + `Ark Plugin`: Ark 插件,满足特定目录格式要求的 `Fat Jar`,使用官方提供的 `Maven` 插件 `sofa-ark-plugin-maven-plugin` 可以将一个或多个普通的 `Java  Jar` 包打包成一个标准格式的 `Ark Plugin`; `Ark Plugin` 会包含一份配置文件,通常包括插件类导入导出配置、插件启动优先级等;运行时,Ark 容器会使用独立的 `PluginClassLoader` 加载插件,并根据插件配置构建类加载索引表,从而使插件与插件、插件与应用之间相互隔离; + `Ark Biz`: Ark 业务模块,满足特定目录格式要求的 `Fat Jar` ,使用官方提供的 `Maven` 插件 `sofa-ark-maven-plugin` 可以将工程应用打包成一个标准格式的 `Ark-Biz` 包;是工程应用模块及其依赖包的组织单元,包含应用启动所需的所有依赖和配置; 在运行时,`Ark Container` 优先启动,自动解析 classpath 包含的 `Ark Plugin` 和 `Ark Biz`,并读取他们的配置,构建类加载索引关系;然后使用独立的 ClassLoader 加载他们并按优先级配置依次启动;需要指出的是,`Ark Plugin` 优先 `Ark Biz` 被加载启动;`Ark Plugin` 之间是双向类索引关系,即可以相互委托对方加载所需的类;`Ark Plugin` 和 `Ark Biz` 是单向类索引关系,即只允许 `Ark Biz` 索引 `Ark Plugin` 加载的类,反之则不允许。 ## 场景 ### 包冲突 SOFAArk初衷是为了解决包冲突问题,那什么情况下可以使用 SOFAArk 以及如何使用呢? 假设如下场景,如果工程需要引入两个三方包:A 和 B,但是 A 需要依赖版本号为 0.1 的 C 包,而恰好 B 需要依赖版本号为 0.2 的 C 包,且 C 包的这两个版本无法兼容: ![conflict](resource/SOFA-Ark-Conflict.png) 此时,即可使用 SOFAArk 解决该依赖冲突问题;只需要把 A 和版本为 0.1 的 C 包一起打包成一个 `Ark Plugin`,然后让应用工程引入该插件依赖即可; ### 合并部署 SOFAArk 基于类隔离能力,实现了应用的合并部署,可以简单分为静态合并部署和动态合并部署,介绍如下。 #### 静态合并部署 在实际开发过程中,经常会出现多个团队合作开发同一款产品,他们各自负责不同的功能模块,这些功能模块通常可以独立开发,但是运行时需要作为一个整体的应用运行。在这种情况下,所有团队需要协商统一技术栈及各自的二方包版本,这无疑增加了开发和联调的成本。为了让开发人员专注自身功能业务的开发,理想情况下开发人员希望能像开发独立应用一样,仅定义好对外交互接口,而不用考虑和其他功能模块出现的版本冲突、技术栈不统一等问题。正是基于这种场景,SOFAArk 提供了静态合并部署能力,应用可以依赖其他应用打成的 Biz 包,而当自身被打成 Ark 包时,可以将其他应用 Biz 包一并打入,启动时,则会根据优先级依次启动各应用。由于每个应用使用独立的 BizClassLoader 加载,因此不需要考虑依赖冲突或者技术栈不统一问题。应用之间则通过 `SofaService/SofaReference` JVM 服务进行交互。 #### 动态合并部署 动态合并部署区别于静态合并部署最大的一点是,在应用运行时可以通过 API 或者配置中心(Zookeeper)来控制应用的部署和卸载。动态合并部署的设计理念图如下: ![life-arch](resource/life-arch.png) 无论是静态还是动态合并部署都会有宿主应用(master app)的概念, 如果 Ark 包只打包了一个 Biz,则该 Biz 默认成为宿主应用。如果 Ark 包打包了多个 Biz 包,需要配置指定宿主应用。宿主应用不允许被卸载,一般而言,宿主应用会作为流量入口的中台系统,具体的服务实现会放在不同的动态 Biz 中,供宿主应用调用。宿主应用可以使用 SOFAArk 提供的客户端 API 实现动态应用的部署和卸载。除了 API, SOFAArk 提供了 Config Plugin,用于对接配置中心(目前支持 Zookeeper),运行时接受动态配置。Config Plugin 会解析下发的配置,控制动态模块的部署和卸载。 随着近几年 模块化、Serverless 技术的兴起,蚂蚁集团在应用研发领域进行了持续的建设和探索,基于 SOFAArk 动态合并部署技术打造了比较成熟的 Koupleless 技术体系,去深入解决企业的研发和运维效率问题。其核心方式是通过快速热部署、动态服务发布等技术,将应用从代码结构和开发者阵型划分为模块和基座。其中基座为业务模块提供计算环境并屏蔽基础设施,让模块开发者不用感知机器和容量等底层设施而专注于某个功能模块的开发迭代来帮助业务快速向前发展。 在应用架构领域,不可避免的问题是应用随着业务的复杂度不断增加,研发运维的过程中的问题会不断暴露出来。首先我们看一下普通应用研发和运维过程中的流程是什么样的: ![image](https://user-images.githubusercontent.com/101314559/172528801-065a18ee-3ce7-46b9-9b78-3f66f9955c97.png) 如图所示,从需求到设计、开发、线下测试,再到发布线上的研发运维不断反馈、循环迭代的过程。可以简化为开发同学提交代码到代码仓库,在线下做并行的验证测试,测试通过之后在线上发布,发布过程是串行的,只能够有一个发布窗口,这样的过程在应用体量业务还不太复杂的情况下问题,并不是很明显。 但当业务复杂度不断增加,普通应用迭代过程在会出现一些新的问题,如下图: ![image](https://user-images.githubusercontent.com/101314559/172525833-311229f1-c631-4170-a16d-1e6d7550b6bc.png) 1. 管理成本高:需求管理、代码管理、人员管理。 2. 时间成本高:线上验证与发布互相阻塞。单次启动慢。 3. 变更风险高:一次变更涉及所有代码。一次变更涉及所有机器。 另外,由于这些问题是因为多个业务与研发任务耦合在某些单点上导致的,研发运维的成本随着业务的复杂度呈现出指数增长的特点: ![image](https://user-images.githubusercontent.com/101314559/172529176-882bd36b-05a6-4450-aa53-24ef64a7e326.png) 通过借助 SOFAArk 框架将应用拆分成基座和模块,同时将应用里的接口按场景维度做分组,使得业务可以按一组接口的粒度进行极速发布运维以及资源按需隔离。 ![image](https://user-images.githubusercontent.com/101314559/172529808-e09349c2-ff07-4431-8f5b-a1786cd0cfe5.png) 从这张图里可以看到 Serverless 应用拆分的形态,通过把一个普通的 Java 应用拆出多个模块,进一步对应用进行了拆分:基座和模块,对应的研发人员也划分为基座开发者和模块开发者。 基座负责沉淀通用的逻辑,为模块提供计算和环境,并为模块开发者屏蔽基础设施,让模块开发者不需要关心容量和资源等。各个模块则是独立的代码仓库,可以进行独立的研发运维,这样研发运维粒度就得到了精细化,并且由于基座为模块屏蔽了环境与基础设施,模块开发者可以专注于业务开发从而提高了业务创新效率。Koupleless 开源版首个 1.0 版本计划已经发布,欢迎大家一起来建设社区 Koupleless 模块化技术。 ## 快速开始 * [基于多 Ark Plugin 解决类冲突](https://github.com/sofastack-guides/sofa-ark-class-isolation) * [基于普通的 Maven 应用构建 Ark Plugin](https://github.com/sofastack-guides/sofa-ark-samples/tree/master/sample-ark-plugin) ## 社区 * [Gitter channel](https://gitter.im/sofa-ark/Lobby) * [Issues](https://github.com/sofastack/sofa-ark/issues) * [钉钉群] ## 贡献 * [代码贡献](./CONTRIBUTING.md) : SOFAArk 开发参与说明书 ## 文档 * [SOFAArk 用户手册(中文)](http://www.sofastack.tech/sofa-boot/docs/sofa-ark-readme) : SOFAArk 用户手册及功能特性详细说明 * [SOFAArk2.0 升级](https://www.sofastack.tech/projects/sofa-boot/sofa-ark-migration-guide/) : SOFAArk1.0升级到2.0 操作详细说明 ## 致谢 SOFAArk 类隔离框架设计实现主要基于 OSGi 规范及蚂蚁金服的 CloudEngine 容器;同时也参考了 Spring Boot 及阿里的 PandoraBoot,感谢以上产品工作者的辛勤付出。 ================================================ FILE: README_EN.md ================================================ # SOFAArk Project [![Build Status](https://travis-ci.org/sofastack/sofa-ark.svg?branch=master)](https://travis-ci.org/sofastack/sofa-ark) [![Coverage Status](https://codecov.io/gh/sofastack/sofa-ark/branch/master/graph/badge.svg)](https://codecov.io/gh/sofastack/sofa-ark/branch/master/graph/badge.svg) [![Gitter](https://img.shields.io/badge/chat-on%20gitter-orange.svg)](https://gitter.im/sofa-ark/Lobby) ![license](https://img.shields.io/badge/license-Apache--2.0-green.svg) ![maven](https://img.shields.io/nexus/r/https/oss.sonatype.org/com.alipay.sofa/sofa-ark-all.svg) SOFAArk is a light-weight,java based classloader isolation framework open sourced by Ant Financial. Please visit [https://alipay.github.io/sofastack.github.io/](https://alipay.github.io/sofastack.github.io/) ## Background In Java world, dependency is always a problem, and can cause various errors, such as `LinkageError`, `NoSuchMethodError` etc. There are many ways to solve the dependency problems, the Spring Boot's way is using a dependency management to manage all the dependencies, make sure that all the dependencies in the dependency management will not conflict and can work pretty well. This is quite a simple and efficient way, it can cover most scenario, but there is some exceptions. For example, there is a project that need protobuf version 2 and protobuf version 3, and because protobuf version 3 is not compatible with version 2, so the project can not simply upgrade the protobuf to version 3 to solve the problem. There is same problem for hessian version 3 and version 4. To cover those exceptions, we need to introduce a classloader isolation way, make different version of a framework loaded by different classloader. There are many framework that can do classloader isolation, perhaps the most famous one is OSGi, but OSGi classloader schema is too complex, beside classloader isolation, it also has ability to do hot deploy and a lot of other functionalities that we actually don't want. So this is the origin of SOFAArk, it's goal is to use a light-weight classloader isolation mechanism to solve the problem that Spring Boot did not solve. And just a remind that SOFAArk is not bind to Spring Boot, actually it is a more general classloader isolation framework that can be used with any other frameworks too. ## How SOFAArk Works There are three concepts in SOFAArk: `Ark Container`, `Ark-Plugin` and `Ark-Biz`; they are organized as what the following graph shows: ![framework](resource/SOFA-Ark-Framework.png) First of all, we explain what roles these concepts play; + `Ark Container`: It's the runtime manager of total framework; it will startup in the first place, then it resolves `Ark Plugin` and `Ark Biz` in classpath and deploys them. + `Ark Plugin`: A fat jar packaged by `sofa-ark-plugin-maven-plugin`, generally it would bring with a class-index configuration which describes what class would be exported and imported. `Ark Plugin` can resolve classes from each other. + `Ark Biz`: A fat jar packaged by `sofa-ark-maven-plugin`, it mainly contains all staff what a project need in runtime. `Ark Biz` can resolve classes form `Ark Plugin`, but not inverse. In runtime, `Ark Container` would automatically recognize `Ark-Plugin` and `Ark-Biz` in classpath, and load them with the independent classloader. According to configurations brought by `Ark Plugin` and `Ark Biz`, `Ark Container` would build a class-index table, so they can be isolated well. For example, if a project has two dependencies of A and B, but A depends on C (version = 0.1) and B depends on C (version = 0.2), so conflicts maybe emerge. ![conflict](resource/SOFA-Ark-Conflict.png) In this situation, we just repackage the dependencies of A and C(version=0.1) as an ark-plugin, and add the dependency of the `ark-plugin` to project, then this conflict would be avoided. ## Sample * [Sample projects](sofa-ark-samples) * [Ark Plugin Based On Maven Project](sofa-ark-samples/sample-ark-plugin) - Sample Project for Ark-Plugin * [Ark Based On Spring Boot](sofa-ark-samples/sample-springboot-ark) Sample Project for Ark based on Spring Boot Project ## Community * [Gitter channel](https://gitter.im/sofa-ark/Lobby) - Online chat room with SOFAArk developers. * [Issues](https://github.com/sofastack/sofa-ark/issues) ## Contribution * [Contributing](./CONTRIBUTING.md) : Guides for contributing to SOFAArk. ## Documentation * [SOFAArk 用户手册(中文)](https://alipay.github.io/sofastack.github.io/docs/): Describe how to used SOFAArk and its features. ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Reporting a Vulnerability If you have apprehensions regarding SOFAStack's security or you discover vulnerability or potential threat, don’t hesitate to get in touch with us by dropping a mail at sofastack@antgroup.com. In the mail, specify the description of the issue or potential threat. You are also urged to recommend the way to reproduce and replicate the issue. The SOFAStack community will get back to you after assessing and analysing the findings. PLEASE PAY ATTENTION to report the security issue on the security email before disclosing it on public domain. ================================================ FILE: change_version.sh ================================================ #!/bin/bash shopt -s expand_aliases if [ ! -n "$1" ] ;then echo "Please enter a version" exit 1 else echo "The updated version is $1 !" fi currentVersion=`sed -n '//p' pom.xml | cut -d '>' -f2 | cut -d '<' -f1` echo "The current version is $currentVersion" if [ `uname` == "Darwin" ] ;then echo "This is OS X" alias sed='sed -i ""' else echo "This is Linux" alias sed='sed -i' fi for filename in `find . -name "README*.md"`;do echo "Deal with $filename" sed "/badge\/maven/! s/$currentVersion/$1/" $filename done ================================================ FILE: check_format.sh ================================================ #!/bin/sh BASEDIR=$(dirname $0) cd ${BASEDIR} # make sure git has no un commit files if [ -n "$(git status --untracked-files=no --porcelain)" ]; then echo "Please commit your change before run this shell, un commit files:" git status --untracked-files=no --porcelain echo "Please run ## mvn clean install -DskipTests -Dmaven.javadoc.skip=true -B -U && sh ./check_format.sh ## locally, then push it." exit -1 fi ================================================ FILE: codecov.yml ================================================ ignore: - "pom.xml" ================================================ FILE: pom.xml ================================================ 4.0.0 com.alipay.sofa sofa-ark ${sofa.ark.version} pom sofa-ark-bom sofa-ark-parent sofa-ark-plugin 2.3.2 2.3.0 UTF-8 1.8 3.0 0.4 3.1 3.0.0 3.2.0 1.6.13 1.6 0.8.4 1.5.0 ${project.groupId}:${project.artifactId} A light-weight, java based classloader-isolated framework open-sourced by Ant Financial. https://github.com/sofastack/sofa-ark The Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt qilong qilong.zql@antfin.com Ant Financial https://www.alipay.com/ abby.zh abby.zh@antfin.com Ant Financial https://www.alipay.com/ scm:git:git://github.com/sofastack/sofa-ark.git scm:git:ssh://github.com/sofastack/sofa-ark.git http://github.com/sofastack/sofa-ark/tree/master com.mycila license-maven-plugin ${license.maven.plugin} generate-sources remove format true
${user.dir}/HEADER
**/src/main/java/** **/src/test/java/** true SLASHSTAR_STYLE
com.googlecode.maven-java-formatter-plugin maven-java-formatter-plugin ${maven.java.formatter.plugin} format ${user.dir}/Formatter.xml ${project.encoding} org.codehaus.mojo flatten-maven-plugin ${flatten-maven-plugin.version} flatten process-resources flatten flatten.clean clean clean true true resolveCiFriendliesOnly org.apache.maven.plugins maven-compiler-plugin ${maven.compiler.plugin} ${project.encoding} ${java.version} ${java.version} org.apache.maven.plugins maven-source-plugin ${maven.source.plugin} attach-sources jar org.apache.maven.plugins maven-javadoc-plugin ${maven.javadoc.plugin} attach-javadocs jar UTF-8 UTF-8 UTF-8 none org.jacoco jacoco-maven-plugin ${jacoco.maven.plugin} false com/alipay/sofa/ark/config/*.class com/alipay/sofa/ark/config/util/*.class com/alipay/sofa/ark/config/zk/*.class com/alipay/sofa/ark/support/listener/ArkTestNG*.class com/alipay/sofa/ark/common/log/ArkLogger.class default-prepare-agent prepare-agent default-report test report-aggregate
release org.sonatype.plugins nexus-staging-maven-plugin ${maven.staging.plugin} true ossrh https://ossrh-staging-api.central.sonatype.com/ false org.apache.maven.plugins maven-gpg-plugin ${maven.gpg.pluign} sign-artifacts verify sign --pinentry-mode loopback ossrh https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/ true maven-snapshot https://central.sonatype.com/repository/maven-snapshots/ snapshot org.sonatype.plugins nexus-staging-maven-plugin ${maven.staging.plugin} true ossrh https://ossrh-staging-api.central.sonatype.com/ false org.apache.maven.plugins maven-gpg-plugin ${maven.gpg.pluign} sign-artifacts verify sign --pinentry-mode loopback ossrh https://central.sonatype.com/repository/maven-snapshots/ ossrh https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/ true maven-snapshot https://central.sonatype.com/repository/maven-snapshots/ true maven-snapshot https://central.sonatype.com/repository/maven-snapshots/ default true true maven-snapshot https://oss.sonatype.org/content/repositories/snapshots true maven-snapshot https://oss.sonatype.org/content/repositories/snapshots
================================================ FILE: sofa-ark-bom/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-bom` **Packaging**: `pom` This is the Bill of Materials (BOM) module for SOFAArk. It provides centralized dependency version management for the entire SOFAArk project. ## Purpose - Centralized version management for all SOFAArk modules and third-party dependencies - Ensures consistent dependency versions across all modules - Simplifies dependency declarations in other modules ## Key Dependencies Managed ### SOFAArk Modules - `sofa-ark-all`, `sofa-ark-spi`, `sofa-ark-api`, `sofa-ark-container`, `sofa-ark-archive` - `sofa-ark-common`, `sofa-ark-exception` - Maven plugins: `sofa-ark-maven-plugin`, `sofa-ark-plugin-maven-plugin` - Spring Boot integration modules ### Third-Party Libraries - Google Guice 6.0.0 (DI framework) - Guava 33.0.0-jre - ASM 9.4 (bytecode manipulation) - Spring Boot 2.7.14 - Logback 1.2.13, SLF4J 1.7.32 - Maven Core/Plugin dependencies 3.8.1 - Netty 4.1.109.Final - JGit 5.13.3 ## Usage Other SOFAArk modules inherit from this BOM: ```xml sofa-ark-bom com.alipay.sofa ${sofa.ark.version} ``` ## When to Modify - Adding new third-party dependencies that are shared across modules - Updating dependency versions - Adding new SOFAArk modules to dependency management ================================================ FILE: sofa-ark-bom/pom.xml ================================================ sofa-ark com.alipay.sofa ${sofa.ark.version} 4.0.0 sofa-ark-bom pom ${project.groupId}:${project.artifactId} 1.7.32 3.2.0 1.2.13 6.0.0 9.4 2.7 3.3.1 3.1.0 3.8.1 3.8.1 3.5.1 3.8.1 3.8.1 3.6.1 3.2.0 3.2.0 3.3.0 0.0.7 2.1.1 5.13.3.202401111512-r 4.13.1 6.14.3 3.6.0 1.16.0 2.4 3.6.1 2.22.2 4.1.109.Final 0.9.19.RELEASE 2.7.14 org.springframework.boot spring-boot ${spring.boot.version} com.alipay.sofa sofa-ark-all ${project.version} com.alipay.sofa sofa-ark-core ${project.version} pom com.alipay.sofa sofa-ark-core-impl ${project.version} pom com.alipay.sofa sofa-ark-support ${project.version} pom com.alipay.sofa sofa-ark-common ${project.version} com.alipay.sofa sofa-ark-exception ${project.version} com.alipay.sofa sofa-ark-spi ${project.version} com.alipay.sofa sofa-ark-api ${project.version} com.alipay.sofa sofa-ark-archive ${project.version} com.alipay.sofa sofa-ark-container ${project.version} com.alipay.sofa sofa-ark-support-starter ${project.version} com.alipay.sofa sofa-ark-maven-plugin ${project.version} com.alipay.sofa sofa-ark-plugin-maven-plugin ${project.version} com.alipay.sofa sofa-ark-springboot-starter ${project.version} com.alipay.sofa sofa-ark-tools ${project.version} com.alipay.sofa sofa-ark-common-springboot ${project.version} com.alipay.sofa sofa-ark-compatible-springboot1 ${project.version} com.alipay.sofa sofa-ark-compatible-springboot2 ${project.version} com.alipay.sofa config-ark-plugin ${project.version} com.alipay.sofa web-ark-plugin ${project.version} com.google.inject guice ${guice.version} com.google.guava guava 33.0.0-jre org.ow2.asm asm ${asm.version} commons-io commons-io ${commons.io.version} org.apache.commons commons-lang3 ${commons.lang3.version} io.netty netty-all ${netty.version} org.slf4j slf4j-api ${log4j.version} ch.qos.logback logback-classic ${logback.version} ch.qos.logback logback-core ${logback.version} com.alipay.sofa log-sofa-boot-starter ${log.sofa.starter.version} org.springframework.boot spring-boot org.apache.maven.shared maven-invoker ${maven.invoker.version} org.apache.maven maven-core ${maven.core.version} org.apache.maven.plugin-tools maven-plugin-annotations ${maven.plugin.annotations.version} org.apache.maven.plugins maven-dependency-plugin ${maven.dependency.plugin.version} org.apache.maven maven-archiver ${maven.archiver.version} org.apache.maven.shared maven-common-artifact-filters ${maven.common.artifact.filters.version} org.apache.maven maven-plugin-api ${maven.plugin.api.version} org.apache.maven maven-model ${maven.model.version} org.apache.maven maven-artifact ${maven.artifact.version} org.codehaus.plexus plexus-component-annotations ${plexus.component.annotations.version} org.codehaus.plexus plexus-utils ${plexus.utils.version} org.sonatype.plexus plexus-build-api ${plexus.build.api.version} org.eclipse.jgit org.eclipse.jgit ${eclipse.jgit.version} org.springframework.boot spring-boot-loader ${spring.boot.version} junit junit ${junit.version} org.testng testng ${testng.version} org.mockito mockito-inline ${mockito.version} test org.mockito mockito-core ${mockito.version} test com.github.stefanbirkner system-rules ${system.rules.version} test com.fasterxml.jackson.core jackson-databind 2.14.3 org.yaml snakeyaml 2.1 org.apache.maven.plugins maven-assembly-plugin ${maven.assembly.plugin} org.apache.maven.plugins maven-plugin-plugin ${maven.plugin.plugin} org.apache.maven.plugins maven-surefire-plugin ${surefire.version} ================================================ FILE: sofa-ark-parent/assembly/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-all` **Package**: Assembly module (no Java code) This module aggregates all core SOFAArk modules into a single JAR that becomes the Ark Container classpath. ## Purpose - Create the aggregated `sofa-ark-all.jar` - This JAR is packaged into every Ark executable JAR - Contains all core runtime dependencies ## Included Modules The assembly includes: - `sofa-ark-common` - Utilities - `sofa-ark-exception` - Exceptions - `sofa-ark-spi` - Service interfaces - `sofa-ark-api` - Public API - `sofa-ark-archive` - Archive handling - `sofa-ark-container` - Core container implementation ## Build Configuration Uses `maven-assembly-plugin` with: - Assembly descriptor: `src/main/assembly/assembly.xml` - Creates single JAR without classifier - Adds manifest entries: `ArkVersion`, `Timestamp` ## Usage in Ark Packages When `sofa-ark-maven-plugin` builds an Ark package: 1. Resolves `sofa-ark-all` artifact 2. Packages it into `SOFA-ARK-CONTAINER/` directory 3. This becomes the container classpath at runtime ## Dependencies All core modules are dependencies (see above). ================================================ FILE: sofa-ark-parent/assembly/pom.xml ================================================ 4.0.0 sofa-ark-parent com.alipay.sofa ${sofa.ark.version} sofa-ark-all ${project.groupId}:${project.artifactId} sofa-ark-all com.alipay.sofa sofa-ark-common com.alipay.sofa sofa-ark-exception com.alipay.sofa sofa-ark-spi com.alipay.sofa sofa-ark-archive com.alipay.sofa sofa-ark-container org.apache.maven.plugins maven-assembly-plugin ${maven.assembly.plugin} make-assembly package single ${sofa.ark.name} src/main/assembly/assembly.xml true true ${project.version} ${maven.build.timestamp} true false ================================================ FILE: sofa-ark-parent/assembly/src/main/assembly/assembly.xml ================================================ sofa-ark-container jar dir false false lib src/main/assembly/mark com/alipay/sofa/ark/container ================================================ FILE: sofa-ark-parent/assembly/src/main/assembly/mark ================================================ a mark file included in sofa-ark-all.jar ================================================ FILE: sofa-ark-parent/core/api/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-api` **Package**: `com.alipay.sofa.ark.api` This module provides the public API for SOFAArk operations. It is the main entry point for external code to interact with the Ark container runtime. ## Purpose - Public API for managing Ark business modules (Biz) - Configuration management through `ArkConfigs` - Response models for API operations ## Key Classes ### `ArkClient` Main API for runtime operations on business modules: - `installBiz(File bizFile)` - Install a new business module - `uninstallBiz(String bizName, String bizVersion)` - Remove a business module - `switchBiz(String bizName, String bizVersion)` - Activate a specific biz version - `checkBiz()` - Query installed business modules - `installPlugin(PluginOperation)` - Install a plugin dynamically - `invocationReplay(String version, Replay replay)` - Invoke code with specific biz version context ### `ArkConfigs` Configuration management: - `getStringValue(String key)` - Get configuration value - `setSystemProperty(String key, String value)` - Set system properties ### `ClientResponse` Response wrapper for API operations with status code and message. ### `ResponseCode` Enum defining response codes: `SUCCESS`, `FAILED`, `REPEAT_BIZ`, `NOT_FOUND_BIZ`, `ILLEGAL_STATE_BIZ` ## Dependencies - `sofa-ark-spi` - Service Provider Interfaces - `sofa-ark-common` - Common utilities ## Used By - `sofa-ark-container` - Implements the services behind this API - `config-ark-plugin` - Uses API for dynamic module management - Application code interacting with Ark runtime ================================================ FILE: sofa-ark-parent/core/api/pom.xml ================================================ sofa-ark-core com.alipay.sofa ${sofa.ark.version} 4.0.0 sofa-ark-api ${project.groupId}:${project.artifactId} com.alipay.sofa sofa-ark-spi com.alipay.sofa sofa-ark-common junit junit test ================================================ FILE: sofa-ark-parent/core/api/src/main/java/com/alipay/sofa/ark/api/ArkClient.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.api; import com.alipay.sofa.ark.common.log.ArkLogger; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.BizIdentityUtils; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.biz.AfterBizSwitchEvent; import com.alipay.sofa.ark.spi.event.biz.BeforeBizSwitchEvent; import com.alipay.sofa.ark.spi.model.*; import com.alipay.sofa.ark.spi.replay.Replay; import com.alipay.sofa.ark.spi.replay.ReplayContext; import com.alipay.sofa.ark.spi.service.biz.BizFactoryService; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import com.alipay.sofa.ark.spi.service.injection.InjectionService; import com.alipay.sofa.ark.spi.service.plugin.PluginFactoryService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.text.SimpleDateFormat; import java.util.*; import static com.alipay.sofa.ark.spi.constant.Constants.AUTO_UNINSTALL_WHEN_FAILED_ENABLE; /** * API used to operate biz * * @author qilong.zql * @since 0.6.0 */ public class ArkClient { private static BizManagerService bizManagerService; private static BizFactoryService bizFactoryService; private static PluginManagerService pluginManagerService; private static PluginFactoryService pluginFactoryService; private static Biz masterBiz; private static InjectionService injectionService; private static String[] arguments; /** * in some case like multi-tenant jdk, we need to set envs for biz */ private static Map envs; private static EventAdminService eventAdminService; private static File getBizInstallDirectory() { String configDir = ArkConfigs.getStringValue(Constants.CONFIG_INSTALL_BIZ_DIR); return StringUtils.isEmpty(configDir) ? FileUtils.createTempDir("sofa-ark") : FileUtils .mkdir(configDir); } public static File createBizSaveFile(String bizName, String bizVersion, String fileSuffix) { String suffix = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()); if (!StringUtils.isEmpty(fileSuffix)) { suffix = fileSuffix; } File bizInstallDirectory = getBizInstallDirectory(); return new File(bizInstallDirectory, bizName + "-" + bizVersion + "-" + suffix); } public static File createBizSaveFile(String bizName, String bizVersion) { return createBizSaveFile(bizName, bizVersion, null); } public static InjectionService getInjectionService() { return injectionService; } public static void setInjectionService(InjectionService injectionService) { ArkClient.injectionService = injectionService; } public static BizManagerService getBizManagerService() { return bizManagerService; } public static void setBizManagerService(BizManagerService bizManagerService) { ArkClient.bizManagerService = bizManagerService; } public static BizFactoryService getBizFactoryService() { return bizFactoryService; } public static void setBizFactoryService(BizFactoryService bizFactoryService) { ArkClient.bizFactoryService = bizFactoryService; } public static void setPluginManagerService(PluginManagerService pluginManagerService) { ArkClient.pluginManagerService = pluginManagerService; } public static PluginManagerService getPluginManagerService() { return pluginManagerService; } public static PluginFactoryService getPluginFactoryService() { return pluginFactoryService; } public static void setPluginFactoryService(PluginFactoryService pluginFactoryService) { ArkClient.pluginFactoryService = pluginFactoryService; } public static Biz getMasterBiz() { return masterBiz; } public static void setMasterBiz(Biz masterBiz) { ArkClient.masterBiz = masterBiz; } public static EventAdminService getEventAdminService() { return eventAdminService; } public static void setEventAdminService(EventAdminService eventAdminService) { ArkClient.eventAdminService = eventAdminService; } public static String[] getArguments() { return arguments; } public static void setArguments(String[] arguments) { ArkClient.arguments = arguments; } public static Map getEnvs() { return envs; } public static void setEnvs(Map envs) { ArkClient.envs = envs; } /** * Install Biz with default arguments and envs throw file * * @param bizFile * @throws Throwable */ public static ClientResponse installBiz(File bizFile) throws Throwable { return installBiz(bizFile, arguments, envs); } public static ClientResponse installBiz(File bizFile, String[] args) throws Throwable { return installBiz(bizFile, args, null); } public static ClientResponse installBiz(File bizFile, String[] args, Map envs) throws Throwable { BizConfig bizConfig = new BizConfig(); bizConfig.setArgs(args); bizConfig.setEnvs(envs); return doInstallBiz(bizFile, bizConfig); } public static ClientResponse installBiz(File bizFile, BizConfig bizConfig) throws Throwable { return doInstallBiz(bizFile, bizConfig); } private static ClientResponse doInstallBiz(File bizFile, BizConfig bizConfig) throws Throwable { AssertUtils.assertNotNull(bizFactoryService, "bizFactoryService must not be null!"); AssertUtils.assertNotNull(bizManagerService, "bizManagerService must not be null!"); AssertUtils.assertNotNull(bizFile, "bizFile must not be null!"); AssertUtils.assertNotNull(bizConfig, "bizConfig must not be null!"); long start = System.currentTimeMillis(); SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss,SSS"); String startDate = sdf.format(new Date(start)); Biz biz = bizFactoryService.createBiz(bizFile, bizConfig); ClientResponse response = new ClientResponse(); if (bizManagerService.getBizByIdentity(biz.getIdentity()) != null || !bizManagerService.registerBiz(biz)) { return response.setCode(ResponseCode.REPEAT_BIZ).setMessage( String.format("Biz: %s has been installed or registered.", biz.getIdentity())); } try { biz.start(bizConfig.getArgs(), bizConfig.getEnvs()); long end = System.currentTimeMillis(); response .setCode(ResponseCode.SUCCESS) .setMessage( String.format("Install Biz: %s success, cost: %s ms, started at: %s", biz.getIdentity(), end - start, startDate)) .setBizInfos(Collections. singleton(biz)); getLogger().info(response.getMessage()); return response; } catch (Throwable throwable) { long end = System.currentTimeMillis(); response.setCode(ResponseCode.FAILED).setMessage( String.format("Install Biz: %s fail,cost: %s ms, started at: %s", biz.getIdentity(), end - start, startDate)); getLogger().error(response.getMessage(), throwable); boolean autoUninstall = Boolean.parseBoolean(ArkConfigs.getStringValue( AUTO_UNINSTALL_WHEN_FAILED_ENABLE, "true")); if (autoUninstall) { try { getLogger().error( String.format("Start Biz: %s failed, try to unInstall this biz.", biz.getIdentity())); biz.stop(); } catch (Throwable e) { getLogger().error(String.format("UnInstall Biz: %s fail.", biz.getIdentity()), e); } } throw throwable; } } /** * Uninstall biz. * * @param bizName * @param bizVersion * @return * @throws Throwable */ public static ClientResponse uninstallBiz(String bizName, String bizVersion) throws Throwable { AssertUtils.assertNotNull(bizFactoryService, "bizFactoryService must not be null!"); AssertUtils.assertNotNull(bizManagerService, "bizManagerService must not be null!"); AssertUtils.assertNotNull(bizName, "bizName must not be null!"); AssertUtils.assertNotNull(bizVersion, "bizVersion must not be null!"); // ignore when uninstall master biz if (bizName.equals(ArkConfigs.getStringValue(Constants.MASTER_BIZ))) { return new ClientResponse().setCode(ResponseCode.FAILED).setMessage( "Master biz must not be uninstalled."); } Biz biz = bizManagerService.getBiz(bizName, bizVersion); ClientResponse response = new ClientResponse().setCode(ResponseCode.NOT_FOUND_BIZ) .setMessage( String.format("Uninstall biz: %s not found.", BizIdentityUtils.generateBizIdentity(bizName, bizVersion))); if (biz != null) { try { biz.stop(); } catch (Throwable throwable) { getLogger().error(String.format("UnInstall Biz: %s fail.", biz.getIdentity()), throwable); throw throwable; } response.setCode(ResponseCode.SUCCESS).setMessage( String.format("Uninstall biz: %s success.", biz.getIdentity())); } getLogger().info(response.getMessage()); return response; } /** * Check all {@link com.alipay.sofa.ark.spi.model.BizInfo} * * @return */ public static ClientResponse checkBiz() { return checkBiz(null, null); } /** * Check all {@link com.alipay.sofa.ark.spi.model.BizInfo} with specified bizName * * @param bizName * @return */ public static ClientResponse checkBiz(String bizName) { return checkBiz(bizName, null); } /** * Check all {@link com.alipay.sofa.ark.spi.model.BizInfo} with specified bizName and bizVersion * * @param bizName * @param bizVersion * @return */ public static ClientResponse checkBiz(String bizName, String bizVersion) { AssertUtils.assertNotNull(bizFactoryService, "bizFactoryService must not be null!"); AssertUtils.assertNotNull(bizManagerService, "bizManagerService must not be null!"); ClientResponse response = new ClientResponse(); Set bizInfoSet = new HashSet<>(); if (bizName != null && bizVersion != null) { Biz biz = bizManagerService.getBiz(bizName, bizVersion); if (biz != null) { bizInfoSet.add(biz); } } else if (bizName != null) { bizInfoSet.addAll(bizManagerService.getBiz(bizName)); } else { bizInfoSet.addAll(bizManagerService.getBizInOrder()); } StringBuilder sb = new StringBuilder(); sb.append(String.format("Biz count=%d", bizInfoSet.size())).append("\n"); for (BizInfo bizInfo : bizInfoSet) { sb.append( String.format("bizName=%s, bizVersion=%s, bizState=%s", bizInfo.getBizName(), bizInfo.getBizVersion(), bizInfo.getBizState())).append("\n"); } response.setCode(ResponseCode.SUCCESS).setBizInfos(bizInfoSet).setMessage(sb.toString()); getLogger().info(String.format("Check Biz: %s", response.getMessage())); return response; } /** * Active biz with specified bizName and bizVersion * * @param bizName * @param bizVersion * @return */ public static ClientResponse switchBiz(String bizName, String bizVersion) { AssertUtils.assertNotNull(bizFactoryService, "bizFactoryService must not be null!"); AssertUtils.assertNotNull(bizManagerService, "bizManagerService must not be null!"); AssertUtils.assertNotNull(bizName, "bizName must not be null!"); AssertUtils.assertNotNull(bizVersion, "bizVersion must not be null!"); Biz biz = bizManagerService.getBiz(bizName, bizVersion); ClientResponse response = new ClientResponse().setCode(ResponseCode.NOT_FOUND_BIZ) .setMessage( String.format("Switch biz: %s not found.", BizIdentityUtils.generateBizIdentity(bizName, bizVersion))); if (biz != null) { if (biz.getBizState() != BizState.ACTIVATED && biz.getBizState() != BizState.DEACTIVATED) { response.setCode(ResponseCode.ILLEGAL_STATE_BIZ).setMessage( String.format("Switch Biz: %s's state must not be %s.", biz.getIdentity(), biz.getBizState())); } else { eventAdminService.sendEvent(new BeforeBizSwitchEvent(biz)); bizManagerService.activeBiz(bizName, bizVersion); eventAdminService.sendEvent(new AfterBizSwitchEvent(biz)); response.setCode(ResponseCode.SUCCESS).setMessage( String.format("Switch biz: %s is activated.", biz.getIdentity())); } } getLogger().info(response.getMessage()); return response; } public static ClientResponse installOperation(BizOperation bizOperation) throws Throwable { return doInstallOperation(bizOperation, arguments, envs); } public static ClientResponse installOperation(BizOperation bizOperation, String[] args) throws Throwable { return doInstallOperation(bizOperation, args, null); } public static ClientResponse installOperation(BizOperation bizOperation, String[] args, Map envs) throws Throwable { return doInstallOperation(bizOperation, args, envs); } private static ClientResponse doInstallOperation(BizOperation bizOperation, String[] args, Map envs) throws Throwable { AssertUtils.isTrue( BizOperation.OperationType.INSTALL.equals(bizOperation.getOperationType()), "Operation type must be install"); File bizFile = null; if (bizOperation.getParameters().get(Constants.CONFIG_BIZ_URL) != null) { URL url = new URL(bizOperation.getParameters().get(Constants.CONFIG_BIZ_URL)); bizFile = ArkClient.createBizSaveFile(bizOperation.getBizName(), bizOperation.getBizVersion()); try (InputStream inputStream = url.openStream()) { FileUtils.copyInputStreamToFile(inputStream, bizFile); } } // prepare extension urls if necessary URL[] extensionUrls = null; if (bizOperation.getParameters().get(Constants.BIZ_EXTENSION_URLS) != null) { Set extensionLibs = StringUtils.strToSet( bizOperation.getParameters().get(Constants.BIZ_EXTENSION_URLS), Constants.COMMA_SPLIT); List urlsList = new ArrayList<>(); if (!extensionLibs.isEmpty()) { for (String extension : extensionLibs) { URL url = new URL(extension); urlsList.add(url); } } extensionUrls = urlsList.toArray(new URL[0]); } BizConfig bizConfig = new BizConfig(); bizConfig.setExtensionUrls(extensionUrls); bizConfig.setArgs(args); bizConfig.setEnvs(envs); return installBiz(bizFile, bizConfig); } public static ClientResponse uninstallOperation(BizOperation bizOperation) throws Throwable { AssertUtils.isTrue( BizOperation.OperationType.UNINSTALL.equals(bizOperation.getOperationType()), "Operation type must be uninstall"); return uninstallBiz(bizOperation.getBizName(), bizOperation.getBizVersion()); } public static ClientResponse switchOperation(BizOperation bizOperation) { AssertUtils.isTrue( BizOperation.OperationType.SWITCH.equals(bizOperation.getOperationType()), "Operation type must be switch"); return switchBiz(bizOperation.getBizName(), bizOperation.getBizVersion()); } public static ClientResponse checkOperation(BizOperation bizOperation) { AssertUtils.isTrue( BizOperation.OperationType.CHECK.equals(bizOperation.getOperationType()), "Operation type must be check"); return checkBiz(bizOperation.getBizName(), bizOperation.getBizVersion()); } public static ClientResponse installPlugin(PluginOperation pluginOperation) throws Exception { AssertUtils.assertNotNull(pluginOperation, "pluginOperation must not be null"); // prepare plugin file File localFile = pluginOperation.getLocalFile(); if (localFile == null && !StringUtils.isEmpty(pluginOperation.getUrl())) { URL url = new URL(pluginOperation.getUrl()); String pluginDir = ArkConfigs.getStringValue(Constants.CONFIG_INSTALL_PLUGIN_DIR); File pluginDirectory = StringUtils.isEmpty(pluginDir) ? FileUtils .createTempDir("sofa-ark") : FileUtils.mkdir(pluginDir); localFile = new File(pluginDirectory, pluginOperation.getPluginName() + "-" + pluginOperation.getPluginVersion() + "-" + System.currentTimeMillis()); try (InputStream inputStream = url.openStream()) { FileUtils.copyInputStreamToFile(inputStream, localFile); } } ClientResponse response = new ClientResponse(); if (localFile == null) { response.setCode(ResponseCode.FAILED).setMessage( String.format("Install Plugin: %s-%s fail, local file is null.", pluginOperation.getPluginName(), pluginOperation.getPluginVersion())); return response; } PluginConfig pluginConfig = new PluginConfig(); if (!StringUtils.isEmpty(pluginOperation.getPluginName())) { pluginConfig.setSpecifiedName(pluginOperation.getPluginName()); } if (!StringUtils.isEmpty(pluginOperation.getPluginVersion())) { pluginConfig.setSpecifiedVersion(pluginOperation.getPluginVersion()); } // prepare extension urls if necessary List extensionLibs = pluginOperation.getExtensionLibs(); List urlsList = new ArrayList<>(); if (extensionLibs != null && !extensionLibs.isEmpty()) { for (String extension : extensionLibs) { URL url = new URL(extension); urlsList.add(url); } } URL[] extensionUrls = urlsList.toArray(new URL[0]); pluginConfig.setExtensionUrls(extensionUrls); long start = System.currentTimeMillis(); SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss,SSS"); String startDate = sdf.format(new Date(start)); // create Plugin plugin = pluginFactoryService.createPlugin(localFile, pluginConfig); // register pluginManagerService.registerPlugin(plugin); // start try { plugin.start(); long end = System.currentTimeMillis(); response.setCode(ResponseCode.SUCCESS).setMessage( String.format("Install Plugin: %s success, cost: %s ms, started at: %s", plugin.getPluginName() + ":" + plugin.getVersion(), end - start, startDate)); getLogger().info(response.getMessage()); } catch (Throwable throwable) { long end = System.currentTimeMillis(); response.setCode(ResponseCode.FAILED).setMessage( String.format("Install Plugin: %s fail,cost: %s ms, started at: %s", plugin.getPluginName() + ":" + plugin.getVersion(), end - start, startDate)); getLogger().error(response.getMessage(), throwable); throw throwable; } return response; } public static ClientResponse checkPlugin() { return checkPlugin(null); } public static ClientResponse checkPlugin(String pluginName) { AssertUtils.assertNotNull(pluginFactoryService, "pluginFactoryService must not be null!"); AssertUtils.assertNotNull(pluginManagerService, "pluginManagerService must not be null!"); ClientResponse response = new ClientResponse(); Set plugins = new HashSet<>(); if (pluginName != null) { Plugin plugin = pluginManagerService.getPluginByName(pluginName); if (plugin != null) { plugins.add(plugin); } } else { plugins.addAll(pluginManagerService.getPluginsInOrder()); } StringBuilder sb = new StringBuilder(); sb.append(String.format("Plugin count=%d", plugins.size())).append("\n"); for (Plugin plugin : plugins) { sb.append( String.format("pluginName=%s, pluginVersion=%s", plugin.getPluginName(), plugin.getVersion())).append("\n"); } response.setCode(ResponseCode.SUCCESS).setPluginInfos(plugins).setMessage(sb.toString()); getLogger().info(String.format("Check Plugin: %s", response.getMessage())); return response; } /** * dynamic invoke by specified version * @param version * @param replay * @return */ public static Object invocationReplay(String version, Replay replay) { try { ReplayContext.set(version); return replay.invoke(); } finally { ReplayContext.unset(); } } private static ArkLogger getLogger() { return ArkLoggerFactory.getDefaultLogger(); } } ================================================ FILE: sofa-ark-parent/core/api/src/main/java/com/alipay/sofa/ark/api/ArkConfigs.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.api; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.constant.Constants; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * @author qilong.zql * @author GengZhang * @since 0.6.0 */ public class ArkConfigs { /** * Global Configuration */ private final static ConcurrentMap CFG = new ConcurrentHashMap(); /** * executed only once */ public static void init(List confFiles) { try { // load file configs for (URL url : confFiles) { loadConfigFile(url.openStream()); } } catch (Exception e) { throw new ArkRuntimeException("Catch Exception when load ArkConfigs", e); } } /** * load conf file * * @param inputStream conf file * @throws IOException loading exception */ private static void loadConfigFile(InputStream inputStream) throws IOException { Properties properties = new Properties(); properties.load(inputStream); for (Object key : properties.keySet()) { CFG.put((String) key, properties.get(key)); } } /** * configure system property * * @param key * @param value */ public static void setSystemProperty(String key, String value) { System.setProperty(key, value); } /** * clear system property * * @param key */ public static String getSystemProperty(String key) { return System.getProperty(key); } /** * Get string value. * * @param primaryKey the primary key * @return the string value */ public static String getStringValue(String primaryKey) { String val = getSystemProperty(primaryKey); if (val == null) { val = (String) CFG.get(primaryKey); } return val; } /** * Get string value. * * @param primaryKey the primary key * @param defaultValue * @return the string value */ public static String getStringValue(String primaryKey, String defaultValue) { String val = getStringValue(primaryKey); return val == null ? defaultValue : val; } /** * Get int value. * * @param primaryKey the primary key * @param defaultValue * @return the int value */ public static int getIntValue(String primaryKey, int defaultValue) { String val = getStringValue(primaryKey); return val == null ? defaultValue : Integer.valueOf(val); } public static boolean getBooleanValue(String primaryKey, boolean defaultValue) { String val = getStringValue(primaryKey); return val == null ? defaultValue : Boolean.valueOf(val); } /** * Get ArkConfigs key set * * @return */ public static Set keySet() { Set keySet = new HashSet<>(CFG.keySet()); keySet.addAll(new HashMap(System.getProperties()).keySet()); return keySet; } /** * put string config * @param key * @param value */ public static void putStringValue(String key, String value) { CFG.put(key, value); } public static boolean isEmbedEnable() { return Boolean.getBoolean(Constants.EMBED_ENABLE); } public static void setEmbedEnable(boolean enable) { System.setProperty(Constants.EMBED_ENABLE, enable ? "true" : "false"); } public static boolean isEmbedStaticBizEnable() { return Boolean.getBoolean(Constants.EMBED_STATIC_BIZ_ENABLE); } public static void setEmbedStaticBizEnable(boolean enable) { System.setProperty(Constants.EMBED_STATIC_BIZ_ENABLE, enable ? "true" : "false"); } public static boolean isBizSpecifyDependentPluginsEnable() { return Boolean.getBoolean(Constants.BIZ_SPECIFY_DEPENDENT_PLUGINS_ENABLE); } } ================================================ FILE: sofa-ark-parent/core/api/src/main/java/com/alipay/sofa/ark/api/ClientResponse.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.api; import com.alipay.sofa.ark.spi.model.BizInfo; import com.alipay.sofa.ark.spi.model.Plugin; import java.util.Set; /** * API operation response * * @author qilong.zql * @since 0.6.0 */ public class ClientResponse { private String message; private ResponseCode code; private Set bizInfos; private Set pluginInfos; public String getMessage() { return message; } public ClientResponse setMessage(String message) { this.message = message; return this; } public ResponseCode getCode() { return code; } public ClientResponse setCode(ResponseCode code) { this.code = code; return this; } public Set getBizInfos() { return bizInfos; } public ClientResponse setBizInfos(Set bizInfos) { this.bizInfos = bizInfos; return this; } public Set getPluginInfos() { return pluginInfos; } public ClientResponse setPluginInfos(Set pluginInfos) { this.pluginInfos = pluginInfos; return this; } } ================================================ FILE: sofa-ark-parent/core/api/src/main/java/com/alipay/sofa/ark/api/ResponseCode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.api; /** * @author qilong.zql * @since 0.6.0 */ public enum ResponseCode { SUCCESS, FAILED, REPEAT_BIZ, NOT_FOUND_BIZ, ILLEGAL_STATE_BIZ; } ================================================ FILE: sofa-ark-parent/core/api/src/test/java/com/alipay/sofa/ark/api/ArkConfigsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.api; import org.junit.Test; import java.net.URL; import static com.alipay.sofa.ark.api.ArkConfigs.getStringValue; import static com.alipay.sofa.ark.api.ArkConfigs.init; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; public class ArkConfigsTest { @Test public void testLoadConfigFile() throws Exception { URL resource = this.getClass().getClassLoader().getResource("test.props"); init(asList(resource)); assertEquals("b123", getStringValue("a123")); assertEquals("d123", getStringValue("c123")); } } ================================================ FILE: sofa-ark-parent/core/api/src/test/resources/test.props ================================================ a123=b123 c123=d123 ================================================ FILE: sofa-ark-parent/core/common/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-common` **Package**: `com.alipay.sofa.ark.common` This module provides common utilities, logging, and shared functionality used across all SOFAArk modules. ## Purpose - Utility classes for file operations, strings, assertions - Logging infrastructure - Common constants and helper methods ## Key Packages ### `common.util` Utility classes: - `StringUtils` - String manipulation utilities - `FileUtils` - File I/O operations - `AssertUtils` - Assertion helpers - `BizIdentityUtils` - Business module identity parsing (`name:version` format) - `ClassUtils` - Class loading utilities - `EnvironmentUtils` - Environment variable handling ### `common.log` Logging infrastructure: - `ArkLogger` - Logger wrapper - `ArkLoggerFactory` - Logger factory for creating Ark loggers ## Dependencies - `sofa-ark-exception` - Exception definitions - `log-sofa-boot-starter` - SOFA logging framework - `slf4j-api` - Logging facade ## Used By Almost all SOFAArk modules depend on this common utilities module: - `sofa-ark-api` - `sofa-ark-container` - `sofa-ark-archive` - `sofa-ark-maven-plugin` - `sofa-ark-plugin-maven-plugin` ================================================ FILE: sofa-ark-parent/core/common/pom.xml ================================================ 4.0.0 sofa-ark-core com.alipay.sofa ${sofa.ark.version} sofa-ark-common ${project.groupId}:${project.artifactId} com.alipay.sofa sofa-ark-spi com.alipay.sofa log-sofa-boot-starter org.springframework.boot spring-boot com.google.inject guice commons-io commons-io ch.qos.logback logback-classic ch.qos.logback logback-core org.mockito mockito-inline ${mockito.version} test junit junit test commons-beanutils commons-beanutils 1.9.4 test ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/adapter/ArkLogbackContextSelector.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.adapter; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.selector.ContextSelector; import ch.qos.logback.core.CoreConstants; import com.alipay.sofa.ark.common.util.StringUtils; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class ArkLogbackContextSelector implements ContextSelector { private static final Map CLASS_LOADER_LOGGER_CONTEXT = new HashMap<>(); private static final String BIZ_CLASS_LOADER = "com.alipay.sofa.ark.container.service.classloader.BizClassLoader"; private static final String CONTAINER_CLASS_LOADER = "com.alipay.sofa.ark.bootstrap.ContainerClassLoader"; private LoggerContext defaultLoggerContext; public ArkLogbackContextSelector(LoggerContext loggerContext) { this.defaultLoggerContext = loggerContext; } @Override public LoggerContext getLoggerContext() { ClassLoader classLoader = this.findClassLoader(); if (classLoader == null) { return defaultLoggerContext; } return getContext(classLoader); } private ClassLoader findClassLoader() { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (classLoader != null && CONTAINER_CLASS_LOADER.equals(classLoader.getClass().getName())) { return null; } if (classLoader != null && BIZ_CLASS_LOADER.equals(classLoader.getClass().getName())) { return classLoader; } Class[] context = new SecurityManager() { @Override public Class[] getClassContext() { return super.getClassContext(); } }.getClassContext(); if (context == null || context.length == 0) { return null; } for (Class cls : context) { if (cls.getClassLoader() != null && BIZ_CLASS_LOADER.equals(cls.getClassLoader().getClass().getName())) { return cls.getClassLoader(); } } return null; } private LoggerContext getContext(ClassLoader cls) { LoggerContext loggerContext = CLASS_LOADER_LOGGER_CONTEXT.get(cls); if (null == loggerContext) { synchronized (ArkLogbackContextSelector.class) { loggerContext = CLASS_LOADER_LOGGER_CONTEXT.get(cls); if (null == loggerContext) { loggerContext = new LoggerContext(); loggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME); CLASS_LOADER_LOGGER_CONTEXT.put(cls, loggerContext); } } } return loggerContext; } @Override public LoggerContext getLoggerContext(String name) { if (StringUtils.isEmpty(name)) { return defaultLoggerContext; } for (ClassLoader classLoader : CLASS_LOADER_LOGGER_CONTEXT.keySet()) { LoggerContext loggerContext = CLASS_LOADER_LOGGER_CONTEXT.get(classLoader); if (name.equals(loggerContext.getName())) { return loggerContext; } } return defaultLoggerContext; } @Override public LoggerContext getDefaultLoggerContext() { return defaultLoggerContext; } @Override public LoggerContext detachLoggerContext(String loggerContextName) { if (StringUtils.isEmpty(loggerContextName)) { return null; } for (ClassLoader classLoader : CLASS_LOADER_LOGGER_CONTEXT.keySet()) { LoggerContext loggerContext = CLASS_LOADER_LOGGER_CONTEXT.get(classLoader); if (loggerContextName.equals(loggerContext.getName())) { return removeContext(classLoader); } } return null; } public LoggerContext removeContext(ClassLoader cls) { if (cls == null) { return null; } return CLASS_LOADER_LOGGER_CONTEXT.remove(cls); } @Override public List getContextNames() { return CLASS_LOADER_LOGGER_CONTEXT.values().stream().map(LoggerContext::getName).collect(Collectors.toList()); } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/guice/AbstractArkGuiceModule.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.guice; import com.google.inject.AbstractModule; /** * Abstract Guice Module for SofaArk * * @author ruoshan * @since 0.1.0 */ public abstract class AbstractArkGuiceModule extends AbstractModule { } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/log/ArkLogger.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.log; import org.slf4j.Logger; import org.slf4j.Marker; /** * Logger Implementation for SOFAArk * * @author ruoshan * @since 0.1.0 */ public class ArkLogger implements Logger { private Logger logger; public ArkLogger(Logger logger) { this.logger = logger; } @Override public String getName() { return logger.getName(); } @Override public boolean isTraceEnabled() { return logger.isTraceEnabled(); } @Override public void trace(String msg) { logger.trace(msg); } @Override public void trace(String format, Object arg) { logger.trace(format, arg); } @Override public void trace(String format, Object arg1, Object arg2) { logger.trace(format, arg1, arg2); } @Override public void trace(String format, Object... arguments) { logger.trace(format, arguments); } @Override public void trace(String msg, Throwable t) { logger.trace(msg, t); } @Override public boolean isTraceEnabled(Marker marker) { return logger.isTraceEnabled(); } @Override public void trace(Marker marker, String msg) { logger.trace(marker, msg); } @Override public void trace(Marker marker, String format, Object arg) { logger.trace(marker, format, arg); } @Override public void trace(Marker marker, String format, Object arg1, Object arg2) { logger.trace(marker, format, arg1, arg2); } @Override public void trace(Marker marker, String format, Object... argArray) { logger.trace(marker, format, argArray); } @Override public void trace(Marker marker, String msg, Throwable t) { logger.trace(marker, msg, t); } @Override public boolean isDebugEnabled() { return logger.isDebugEnabled(); } @Override public void debug(String msg) { logger.debug(msg); } @Override public void debug(String format, Object arg) { logger.debug(format, arg); } @Override public void debug(String format, Object arg1, Object arg2) { logger.debug(format, arg1, arg2); } @Override public void debug(String format, Object... arguments) { logger.debug(format, arguments); } @Override public void debug(String msg, Throwable t) { logger.debug(msg, t); } @Override public boolean isDebugEnabled(Marker marker) { return logger.isDebugEnabled(marker); } @Override public void debug(Marker marker, String msg) { logger.debug(marker, msg); } @Override public void debug(Marker marker, String format, Object arg) { logger.debug(marker, format, arg); } @Override public void debug(Marker marker, String format, Object arg1, Object arg2) { logger.debug(marker, format, arg1, arg2); } @Override public void debug(Marker marker, String format, Object... arguments) { logger.debug(marker, format, arguments); } @Override public void debug(Marker marker, String msg, Throwable t) { logger.debug(marker, msg, t); } @Override public boolean isInfoEnabled() { return logger.isInfoEnabled(); } @Override public void info(String msg) { logger.info(msg); } @Override public void info(String format, Object arg) { logger.info(format, arg); } @Override public void info(String format, Object arg1, Object arg2) { logger.info(format, arg1, arg2); } @Override public void info(String format, Object... arguments) { logger.info(format, arguments); } @Override public void info(String msg, Throwable t) { logger.info(msg, t); } @Override public boolean isInfoEnabled(Marker marker) { return logger.isInfoEnabled(); } @Override public void info(Marker marker, String msg) { logger.info(marker, msg); } @Override public void info(Marker marker, String format, Object arg) { logger.info(marker, format, arg); } @Override public void info(Marker marker, String format, Object arg1, Object arg2) { logger.info(marker, format, arg1, arg2); } @Override public void info(Marker marker, String format, Object... arguments) { logger.info(marker, format, arguments); } @Override public void info(Marker marker, String msg, Throwable t) { logger.info(marker, msg, t); } @Override public boolean isWarnEnabled() { return logger.isWarnEnabled(); } @Override public void warn(String msg) { logger.warn(msg); } @Override public void warn(String format, Object arg) { logger.warn(format, arg); } @Override public void warn(String format, Object... arguments) { logger.warn(format, arguments); } @Override public void warn(String format, Object arg1, Object arg2) { logger.warn(format, arg1, arg2); } @Override public void warn(String msg, Throwable t) { logger.warn(msg, t); } @Override public boolean isWarnEnabled(Marker marker) { return logger.isWarnEnabled(marker); } @Override public void warn(Marker marker, String msg) { logger.warn(marker, msg); } @Override public void warn(Marker marker, String format, Object arg) { logger.warn(marker, format, arg); } @Override public void warn(Marker marker, String format, Object arg1, Object arg2) { logger.warn(marker, format, arg1, arg2); } @Override public void warn(Marker marker, String format, Object... arguments) { logger.warn(marker, format, arguments); } @Override public void warn(Marker marker, String msg, Throwable t) { logger.warn(marker, msg, t); } @Override public boolean isErrorEnabled() { return logger.isErrorEnabled(); } @Override public void error(String msg) { logger.error(msg); } @Override public void error(String format, Object arg) { logger.error(format, arg); } @Override public void error(String format, Object arg1, Object arg2) { logger.error(format, arg1, arg2); } @Override public void error(String format, Object... arguments) { logger.error(format, arguments); } @Override public void error(String msg, Throwable t) { logger.error(msg, t); } @Override public boolean isErrorEnabled(Marker marker) { return logger.isErrorEnabled(marker); } @Override public void error(Marker marker, String msg) { logger.error(marker, msg); } @Override public void error(Marker marker, String format, Object arg) { logger.error(marker, format, arg); } @Override public void error(Marker marker, String format, Object arg1, Object arg2) { logger.error(marker, format, arg1, arg2); } @Override public void error(Marker marker, String format, Object... arguments) { logger.error(marker, format, arguments); } @Override public void error(Marker marker, String msg, Throwable t) { logger.error(marker, msg, t); } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/log/ArkLoggerFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.log; import com.alipay.sofa.common.log.LoggerSpaceManager; /** * LoggerFactory for SOFAArk * * @author ruoshan * @since 0.1.0 */ public class ArkLoggerFactory { public static final String SOFA_ARK_LOGGER_SPACE = "com.alipay.sofa.ark"; private static final String SOFA_ARK_DEFAULT_LOGGER_NAME = "com.alipay.sofa.ark"; public static ArkLogger defaultLogger = getLogger(SOFA_ARK_DEFAULT_LOGGER_NAME); public static ArkLogger getLogger(Class clazz) { if (clazz == null) { return null; } return getLogger(clazz.getCanonicalName()); } public static ArkLogger getLogger(String name) { if (name == null || name.isEmpty()) { return null; } return new ArkLogger(LoggerSpaceManager.getLoggerBySpace(name, SOFA_ARK_LOGGER_SPACE)); } public static ArkLogger getDefaultLogger() { return defaultLogger; } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/thread/CommonThreadPool.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.thread; import com.alipay.sofa.ark.common.util.ThreadPoolUtils; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Execute tasks triggered by ark container and ark plugin. * * @author qilong.zql * @since 0.4.0 */ public class CommonThreadPool { /** * Core size of thread pool * * @see ThreadPoolExecutor#corePoolSize */ private int corePoolSize = 10; /** * Maximum size of thread pool * * @see ThreadPoolExecutor#corePoolSize */ private int maximumPoolSize = 100; /** * @see ThreadPoolExecutor#keepAliveTime */ private int keepAliveTime = 300000; /** * @see ThreadPoolExecutor#getQueue */ private int queueSize = 0; /** * @see ThreadPoolExecutor#threadFactory#threadPoolName */ private String threadPoolName = "CommonProcessor"; /** * @see ThreadPoolExecutor#threadFactory#isDaemon */ private boolean isDaemon = false; /** * @see ThreadPoolExecutor#allowCoreThreadTimeOut */ private boolean allowCoreThreadTimeOut = false; /** * @see ThreadPoolExecutor#prestartAllCoreThreads */ private boolean prestartAllCoreThreads = false; /** * ThreadPoolExecutor */ transient volatile ThreadPoolExecutor executor; private void init() { executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, ThreadPoolUtils.buildQueue(queueSize), new NamedThreadFactory( threadPoolName, isDaemon)); if (allowCoreThreadTimeOut) { executor.allowCoreThreadTimeOut(true); } if (prestartAllCoreThreads) { executor.prestartAllCoreThreads(); } } public int getCorePoolSize() { return corePoolSize; } public CommonThreadPool setCorePoolSize(int corePoolSize) { this.corePoolSize = corePoolSize; return this; } public int getMaximumPoolSize() { return maximumPoolSize; } public CommonThreadPool setMaximumPoolSize(int maximumPoolSize) { this.maximumPoolSize = maximumPoolSize; return this; } public int getKeepAliveTime() { return keepAliveTime; } public CommonThreadPool setKeepAliveTime(int keepAliveTime) { this.keepAliveTime = keepAliveTime; return this; } public int getQueueSize() { return queueSize; } public CommonThreadPool setQueueSize(int queueSize) { this.queueSize = queueSize; return this; } public String getThreadPoolName() { return threadPoolName; } public CommonThreadPool setThreadPoolName(String threadPoolName) { this.threadPoolName = threadPoolName; return this; } public boolean isDaemon() { return isDaemon; } public CommonThreadPool setDaemon(boolean daemon) { isDaemon = daemon; return this; } public boolean isAllowCoreThreadTimeOut() { return allowCoreThreadTimeOut; } public CommonThreadPool setAllowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) { this.allowCoreThreadTimeOut = allowCoreThreadTimeOut; return this; } public boolean isPrestartAllCoreThreads() { return prestartAllCoreThreads; } public CommonThreadPool setPrestartAllCoreThreads(boolean prestartAllCoreThreads) { this.prestartAllCoreThreads = prestartAllCoreThreads; return this; } /** * Gets executor * * @return the executor */ public ThreadPoolExecutor getExecutor() { if (executor == null) { synchronized (this) { if (executor == null) { init(); } } } return executor; } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/thread/NamedThreadFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.thread; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; /** * Common NamedThreadFactory * * @author qilong.zql * @since 0.4.0 */ public class NamedThreadFactory implements ThreadFactory { /** * The global thread pool counter */ private static final AtomicInteger POOL_COUNT = new AtomicInteger(); /** * The current thread pool counter */ private final AtomicInteger threadCount = new AtomicInteger(1); /** * Thread group */ private final ThreadGroup group; /** * Thread name prefix */ private final String namePrefix; /** * Thread daemon option */ private final boolean isDaemon; /** * The first default prefix of thread name */ private final static String FIRST_PREFIX = "SOFA-ARK-"; /** * specify the second prefix of thread name, default the thread created is non-daemon * * @param secondPrefix second prefix of thread name */ public NamedThreadFactory(String secondPrefix) { this(secondPrefix, false); } /** * Construct a named thread factory * * @param secondPrefix second prefix of thread name * @param daemon thread daemon option */ public NamedThreadFactory(String secondPrefix, boolean daemon) { SecurityManager sm = System.getSecurityManager(); group = (sm != null) ? sm.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = FIRST_PREFIX + secondPrefix + "-" + POOL_COUNT.getAndIncrement() + "-T"; isDaemon = daemon; } @Override public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadCount.getAndIncrement(), 0); t.setDaemon(isDaemon); if (t.getPriority() != Thread.NORM_PRIORITY) { t.setPriority(Thread.NORM_PRIORITY); } return t; } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/thread/ThreadPoolManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.thread; import java.util.concurrent.ConcurrentHashMap; /** * Thread Pool Manager * * @author qilong.zql * @since 0.4.0 */ public class ThreadPoolManager { /** * Manager group of {@see CommonThreadPool} */ private static ConcurrentHashMap threadPoolMap = null; /** * Register a thread pool for a unique name. * * @param threadPoolName thread pool name * @param commonThreadPool CommonThreadPool */ public static synchronized void registerThreadPool(String threadPoolName, CommonThreadPool commonThreadPool) { if (threadPoolMap == null) { threadPoolMap = new ConcurrentHashMap<>(16); } threadPoolMap.putIfAbsent(threadPoolName, commonThreadPool); } /** * un-register a thread pool for a given name * * @param threadPoolName thread pool name */ public static synchronized void unRegisterUserThread(String threadPoolName) { if (threadPoolMap != null) { threadPoolMap.remove(threadPoolName); } } /** * Retrieve a thread pool for a given name * * @param threadPoolName thread pool name * @return */ public static CommonThreadPool getThreadPool(String threadPoolName) { return threadPoolMap == null ? null : threadPoolMap.get(threadPoolName); } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/util/AssertUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; /** * * @author ruoshan * @since 0.1.0 */ public class AssertUtils { /** * Validate current object must not be null * * @param instance object instance * @param msg error message * @throws IllegalArgumentException if object instance is null */ public static void assertNotNull(Object instance, String msg) { if (instance == null) { throw new IllegalArgumentException(msg); } } /** * Validate current object must be null * * @param instance object instance * @param msg error message * @throws IllegalArgumentException if object instance is null */ public static void assertNull(Object instance, String msg) { if (instance != null) { throw new IllegalArgumentException(msg); } } /** *

Validate that the argument condition is {@code true}; otherwise * throwing an exception with the specified message. This method is useful when * validating according to an arbitrary boolean expression, such as validating a * primitive number or using your own custom validation expression.

* * @param expression the boolean expression to check * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @throws IllegalArgumentException if expression is {@code false} */ public static void isTrue(final boolean expression, final String message, final Object... values) { if (!expression) { throw new IllegalArgumentException(String.format(message, values)); } } /** *

Validate that the argument condition is {@code false}; otherwise * throwing an exception with the specified message. This method is useful when * validating according to an arbitrary boolean expression, such as validating a * primitive number or using your own custom validation expression.

* * @param expression the boolean expression to check * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @throws IllegalArgumentException if expression is {@code false} */ public static void isFalse(final boolean expression, final String message, final Object... values) { if (expression) { throw new IllegalArgumentException(String.format(message, values)); } } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/util/BizIdentityUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Biz; /** * @author qilong.zql * @since 0.6.0 */ public class BizIdentityUtils { public static String generateBizIdentity(Biz biz) { return generateBizIdentity(biz.getBizName(), biz.getBizVersion()); } public static String generateBizIdentity(String bizName, String bizVersion) { return bizName + Constants.STRING_COLON + bizVersion; } public static boolean isValid(String bizIdentity) { if (StringUtils.isEmpty(bizIdentity)) { return false; } String[] str = bizIdentity.split(Constants.STRING_COLON); if (str.length != 2) { return false; } return !StringUtils.isEmpty(str[0]) && !StringUtils.isEmpty(str[1]); } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/util/ClassLoaderUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import com.alipay.sofa.ark.exception.ArkRuntimeException; import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.List; /** * ClassLoader Util * * @author ruoshan * @since 0.1.0 */ public class ClassLoaderUtils { private static final String JAVA_AGENT_MARK = "-javaagent:"; private static final String JAVA_AGENT_OPTION_MARK = "="; private static final String SKYWALKING_AGENT_JAR = "skywalking-agent.jar"; private static final String[] SKYWALKING_MOUNT_DIR = { "plugins", "activations" }; /** * push ContextClassLoader * * @param newClassLoader new classLoader * @return old classloader */ public static ClassLoader pushContextClassLoader(ClassLoader newClassLoader) { ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(newClassLoader); return oldClassLoader; } /** * set ContextClassLoader back * * @param oldClassLoader old classLoader */ public static void popContextClassLoader(ClassLoader oldClassLoader) { Thread.currentThread().setContextClassLoader(oldClassLoader); } public static URL[] getAgentClassPath() { List inputArguments = AccessController .doPrivileged(new PrivilegedAction>() { @Override public List run() { return ManagementFactory.getRuntimeMXBean().getInputArguments(); } }); List agentPaths = new ArrayList<>(); for (String argument : inputArguments) { if (!argument.startsWith(JAVA_AGENT_MARK)) { continue; } argument = argument.substring(JAVA_AGENT_MARK.length()); try { String path = argument.split(JAVA_AGENT_OPTION_MARK)[0]; URL url = FileUtils.file(path).getCanonicalFile().toURI().toURL(); agentPaths.add(url); processSkyWalking(path, agentPaths); } catch (Throwable e) { throw new ArkRuntimeException("Failed to create java agent classloader", e); } } return agentPaths.toArray(new URL[] {}); } /** * process skywalking agent plugins/activations * @param path * @param agentPaths * @throws MalformedURLException */ public static void processSkyWalking(final String path, final List agentPaths) throws MalformedURLException, IOException { if (path.contains(SKYWALKING_AGENT_JAR)) { for (String mountFolder : SKYWALKING_MOUNT_DIR) { File folder = new File(FileUtils.file(path).getCanonicalFile().getParentFile(), mountFolder); if (folder.exists() && folder.isDirectory()) { String[] jarFileNames = folder.list((dir, name) -> name.endsWith(".jar")); for (String fileName: jarFileNames) { File jarFile = new File(folder, fileName); agentPaths.add(jarFile.toURI().toURL()); } } } } } @SuppressWarnings({ "restriction", "unchecked" }) public static URL[] getURLs(ClassLoader classLoader) { // https://stackoverflow.com/questions/46519092/how-to-get-all-jars-loaded-by-a-java-application-in-java9 if (classLoader instanceof URLClassLoader) { return ((URLClassLoader) classLoader).getURLs(); } // support jdk9+ String classpath = System.getProperty("java.class.path"); String[] classpathEntries = classpath.split(System.getProperty("path.separator")); List classpathURLs = new ArrayList<>(); for (String classpathEntry : classpathEntries) { URL url = null; try { url = FileUtils.file(classpathEntry).toURI().toURL(); } catch (MalformedURLException e) { e.printStackTrace(); throw new ArkRuntimeException("Failed to get urls from " + classLoader, e); } classpathURLs.add(url); } return classpathURLs.toArray(new URL[0]); } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/util/ClassUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import com.alipay.sofa.ark.spi.constant.Constants; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; /** * @author qilong.zql * @since 0.3.0 */ public class ClassUtils { /** * Get package name from a specified class name. * * @param className * @return */ public static String getPackageName(String className) { AssertUtils.isFalse(StringUtils.isEmpty(className), "ClassName should not be empty!"); int index = className.lastIndexOf('.'); if (index > 0) { return className.substring(0, index); } return Constants.DEFAULT_PACKAGE; } /** * find all compiled classes in dir, ignore inner, anonymous and local classes * @param dir directory that stores class files * @return compiled class names */ public static List collectClasses(File dir) throws IOException { List classNames = new ArrayList<>(); Collection classFiles = FileUtils.listFiles(dir, new String[] { "class" }, true); String basePath = dir.getCanonicalPath(); for (File classFile : classFiles) { // Get the relative file path starting after the classes directory String relativePath = classFile.getCanonicalPath().substring(basePath.length() + 1); // Convert file path to class name (replace file separators with dots and remove .class extension) String className = relativePath.replace(File.separatorChar, '.').replaceAll( "\\.class$", ""); // skip inner, anonymous and local classes if (!className.contains("$")) { classNames.add(className); } } return classNames; } public static String getCodeBase(Class cls) { if (cls == null) { return null; } ProtectionDomain domain = cls.getProtectionDomain(); if (domain == null) { return null; } CodeSource source = domain.getCodeSource(); if (source == null) { return null; } URL location = source.getLocation(); if (location == null) { return null; } return location.getFile(); } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/util/EnvironmentUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import java.util.Properties; import static com.alipay.sofa.ark.spi.constant.Constants.TELNET_SERVER_SECURITY_ENABLE; /** * a utils class to get environment properties * * @author qilong.zql * @since 0.4.0 */ public class EnvironmentUtils { private static Properties properties = new Properties(); public static String getProperty(String key) { String value = properties.getProperty(key); if (value == null) { return System.getProperty(key); } return value; } public static String getProperty(String key, String defaultValue) { String value = properties.getProperty(key); if (value == null) { return System.getProperty(key, defaultValue); } return value; } public static void setProperty(String key, String value) { properties.setProperty(key, value); } public static void setSystemProperty(String key, String value) { System.setProperty(key, value); } public static void clearProperty(String key) { properties.remove(key); } public static void clearSystemProperty(String key) { System.clearProperty(key); } public static boolean isOpenSecurity() { return getProperty(TELNET_SERVER_SECURITY_ENABLE, "false").equalsIgnoreCase("true"); } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/util/FileUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import com.alipay.sofa.ark.exception.ArkRuntimeException; import java.io.*; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * Utilities for manipulating files and directories in ark tooling. * * @author Dave Syer * @author Phillip Webb * @author qilong.zql * @author GengZhang */ public class FileUtils { /** * Generate a SHA.1 Hash for a given file. * @param file the file to hash * @return the hash value as a String * @throws IOException if the file cannot be read */ public static String sha1Hash(File file) throws IOException { try { DigestInputStream inputStream = new DigestInputStream(new FileInputStream(file), MessageDigest.getInstance("SHA-1")); try { byte[] buffer = new byte[4098]; while (inputStream.read(buffer) != -1) { //NOPMD // Read the entire stream } return bytesToHex(inputStream.getMessageDigest().digest()); } finally { inputStream.close(); } } catch (NoSuchAlgorithmException ex) { throw new IllegalStateException(ex); } } private static String bytesToHex(byte[] bytes) { StringBuilder hex = new StringBuilder(); for (byte b : bytes) { hex.append(String.format("%02x", b)); } return hex.toString(); } /** * Atomically creates a new directory somewhere beneath the system's * temporary directory (as defined by the {@code java.io.tmpdir} system */ public static synchronized File createTempDir(String subPath) { File baseDir = FileUtils.file(System.getProperty("java.io.tmpdir")); File tempDir = new File(baseDir, subPath); if (tempDir.exists()) { return tempDir; } else if (tempDir.mkdir()) { return tempDir; } throw new ArkRuntimeException("Failed to create temp file"); } /** * {@link org.apache.commons.io.FileUtils#copyInputStreamToFile(InputStream, File)} * @param source * @param destination * @throws IOException */ public static void copyInputStreamToFile(final InputStream source, final File destination) throws IOException { org.apache.commons.io.FileUtils.copyInputStreamToFile(source, destination); } /** * * @param path * @return */ public static String getCompatiblePath(String path) { if (System.getProperty("os.name").toLowerCase().indexOf("window") > -1) { return path.replace("\\", "/"); } return path; } public static File unzip(File root, String targetPath) throws IOException { ZipFile zipFile = null; try { zipFile = new ZipFile(root); Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); if (entry.isDirectory()) { String dirPath = targetPath + File.separator + entry.getName(); File dir = FileUtils.file(dirPath); dir.mkdirs(); } else { InputStream inputStream = null; FileOutputStream fileOutputStream = null; try { inputStream = zipFile.getInputStream(entry); File file = FileUtils.file(targetPath + File.separator + entry.getName()); if (!file.exists()) { File fileParent = file.getParentFile(); if (!fileParent.exists()) { fileParent.mkdirs(); } } file.createNewFile(); fileOutputStream = new FileOutputStream(file); int count; byte[] buf = new byte[8192]; while ((count = inputStream.read(buf)) != -1) { fileOutputStream.write(buf, 0, count); } } finally { if (fileOutputStream != null) { fileOutputStream.flush(); fileOutputStream.close(); } if (inputStream != null) { inputStream.close(); } } } } return FileUtils.file(targetPath); } finally { if (zipFile != null) { zipFile.close(); } } } /** * creates a new directory for given path * * @param dirPath dest path * @return Dir */ public static File mkdir(String dirPath) { if (StringUtils.isEmpty(dirPath)) { return null; } File dir = FileUtils.file(dirPath); if (!dir.exists()) { // Recursive creation dir.mkdirs(); } return dir; } /** * decode the given path if the path has spaces * * @param path dest path * @return decoded path */ public static String decodePath(String path) { try { return URLDecoder.decode(path, StandardCharsets.UTF_8.name()); } catch (UnsupportedEncodingException e) { // return source string when occur an exception return path; } } /** * creates a new file for given path. * first, we check if the path is encoded before creating the File object * * @param path file path * @return new File */ public static File file(String path) { return new File(decodePath(path)); } /** * creates a new file for given path. * * @param parent parent path * @param path child path * @return new File */ public static File file(String parent, String path) { return new File(decodePath(parent), path); } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/util/OrderComparator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import com.alipay.sofa.ark.spi.service.PriorityOrdered; import java.util.Comparator; /** * {@link Comparator} implementation for {@link PriorityOrdered} objects, sorting * by order value ascending. * * @author qilong.zql * @since 0.4.0 */ public class OrderComparator implements Comparator { @Override public int compare(PriorityOrdered o1, PriorityOrdered o2) { return Integer.compare(o1.getPriority(), o2.getPriority()); } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/util/ParseUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import com.alipay.sofa.ark.spi.constant.Constants; import java.util.LinkedHashSet; import java.util.Set; /** * * @author ruoshan * @since 1.0.0 */ public class ParseUtils { /** * Parse package node(exactly match) and stem(with *) * @param candidates candidate packages * @param stems with * packages * @param nodes exactly match packages */ public static void parsePackageNodeAndStem(Set candidates, Set stems, Set nodes) { for (String pkgPattern : candidates) { if (pkgPattern.endsWith(Constants.PACKAGE_PREFIX_MARK)) { stems.add(ClassUtils.getPackageName(pkgPattern)); } else { nodes.add(pkgPattern); } } } /** * Parse resource exactly match and stem(with *) * @param candidates candidate resources * @param prefixStems with xxx/* resources match by prefix * @param suffixStems with *.xxx *\/xxx resources match by suffix * @param resources exactly match resources */ public static void parseResourceAndStem(Set candidates, Set prefixStems, Set suffixStems, Set resources) { for (String candidate : candidates) { // do not support export * if (candidate.equals(Constants.RESOURCE_STEM_MARK)) { continue; } if (candidate.endsWith(Constants.RESOURCE_STEM_MARK)) { prefixStems.add(candidate.substring(0, candidate.length() - Constants.RESOURCE_STEM_MARK.length())); } else if (candidate.startsWith(Constants.RESOURCE_STEM_MARK)) { suffixStems.add(candidate.substring(Constants.RESOURCE_STEM_MARK.length())); } else { resources.add(candidate); } } } public static void parseExcludeConf(LinkedHashSet targetSet, String origin, String confKey) { if (origin.startsWith(confKey + Constants.EQUAL_SPLIT)) { String confVal = origin.split(Constants.EQUAL_SPLIT)[1]; if (!StringUtils.isEmpty(confVal) && !targetSet.contains(confVal)) { targetSet.add(confVal); } } } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/util/PortSelectUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import java.io.IOException; import java.net.ServerSocket; /** * @author qilong.zql * @author khotyn * @since 0.5.0 */ public class PortSelectUtils { /** * The minimum candidate port number of IPv4 */ public static final int MIN_PORT_NUMBER = 1100; /** * The maximum candidate port number of IPv4 */ public static final int MAX_PORT_NUMBER = 65535; /** * Select appropriate port among specify interval * * @param defaultPort specify the starting port * @param maxLength specify the size of interval * @return return available port */ public synchronized static int selectAvailablePort(int defaultPort, int maxLength) { for (int i = defaultPort; i < defaultPort + maxLength; i++) { try { if (available(i)) { return i; } } catch (IllegalArgumentException e) { // Ignore and continue } } return -1; } private static boolean available(int port) { if (port < MIN_PORT_NUMBER || port > MAX_PORT_NUMBER) { throw new IllegalArgumentException("Invalid port: " + port); } try (ServerSocket ss = new ServerSocket(port)) { ss.setReuseAddress(true); return true; } catch (IOException e) { // Do nothing } return false; } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/util/ReflectionUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import com.alipay.sofa.ark.exception.ArkRuntimeException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; /** * @author qilong.zql * @author GengZhang * @since 0.4.0 */ public class ReflectionUtils { /** * Callback interface invoked on each field in the hierarchy. */ public interface FieldCallback { /** * Perform an operation using the given field. * @param field the field to operate on * @throws ArkRuntimeException throw exception when handle with field */ void doWith(Field field) throws ArkRuntimeException; } public static void doWithFields(Class clazz, FieldCallback fc) { AssertUtils.assertNotNull(clazz, "Class should not be null"); Class searchType = clazz; do { Field[] fields = searchType.getDeclaredFields(); for (Field field : fields) { fc.doWith(field); } searchType = searchType.getSuperclass(); } while (searchType != null && searchType != Object.class); } public static void makeAccessible(Field field) { if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier .isFinal(field.getModifiers())) && !field.isAccessible()) { field.setAccessible(true); } } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/util/SimpleByteBuffer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; /** * A helper class, which buffers one line of input. It provides for simple line editing, e.g. * insertion, deletion, backspace, left and right movement * * @author qilong.zql * @since 0.4.0 */ public class SimpleByteBuffer { private final static int BUFFER_CHUNK = 20; private byte[] buffer; private int pos = 0; private int size = 0; public SimpleByteBuffer() { buffer = new byte[BUFFER_CHUNK]; } private void resize() { final byte[] next = new byte[buffer.length << 1]; System.arraycopy(buffer, 0, next, 0, buffer.length); buffer = next; } public void add(byte b) { if (size >= buffer.length) { resize(); } buffer[size++] = b; } public void insert(byte b) { if (size >= buffer.length) { resize(); } final int gap = size - pos; if (gap > 0) { System.arraycopy(buffer, pos, buffer, pos + 1, gap); } buffer[pos++] = b; size++; } public byte goRight() { if (pos < size) { return buffer[pos++]; } return -1; } public boolean goLeft() { if (pos > 0) { pos--; return true; } return false; } public void backSpace() { if (pos > 0) { System.arraycopy(buffer, pos, buffer, pos - 1, size - pos); pos--; size--; } } public void delete() { if (pos < size) { System.arraycopy(buffer, pos + 1, buffer, pos, size - pos); size--; } } public byte[] getBuffer() { byte[] data = new byte[size]; System.arraycopy(buffer, 0, data, 0, size); return data; } public byte[] getAndClearBuffer() { byte[] data = new byte[size]; System.arraycopy(buffer, 0, data, 0, size); size = 0; pos = 0; return data; } public int getPos() { return pos; } public int getSize() { return size; } public int getGap() { return size - pos; } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/util/StringUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import java.util.*; /** * * @author ruoshan * @since 0.1.0 */ public class StringUtils { public static final String EMPTY_STRING = ""; public static final int CHAR_A = 'A'; public static final int CHAR_Z = 'Z'; public static final int CASE_GAP = 32; public static final String CR = "\r"; /** *

Checks if a String is empty ("") or null.

* * @param str the String to check, may be null * @return true if the String is empty or null */ public static boolean isEmpty(String str) { return str == null || str.trim().length() == 0; } /** *

Checks whether two String are equal.

* @param a comparator string a * @param b comparator string b * @return whether two string equals */ public static boolean isSameStr(String a, String b) { if (a == null && b != null) { return false; } if (a == null && b == null) { return true; } return a.equals(b); } public static String setToStr(Set stringSet, String delimiter) { return setToStr(stringSet, delimiter, EMPTY_STRING); } public static boolean contains(String sourceStr, String searchStr) { if (sourceStr != null && searchStr != null) { return sourceStr.contains(searchStr); } else { return false; } } /** *

Transform a string set to a long string separated by delimiter

* @param stringSet * @param delimiter * @param defaultRet if stringSet is empty, return defaultRet * @return */ public static String setToStr(Set stringSet, String delimiter, String defaultRet) { if (stringSet == null || stringSet.isEmpty()) { return defaultRet; } AssertUtils.assertNotNull(delimiter, "Delimiter should not be null."); StringBuilder sb = new StringBuilder(); for (String str : stringSet) { sb.append(str.trim()).append(delimiter); } return sb.toString().substring(0, sb.length() - delimiter.length()); } public static Set strToSet(String str, String delimiter) { return new LinkedHashSet<>(strToList(str, delimiter)); } public static List strToList(String str, String delimiter) { if (str == null || str.isEmpty()) { return Collections.emptyList(); } List stringList = new ArrayList<>(); for (String s : str.split(delimiter)) { stringList.add(s.trim()); } return stringList; } /** * Check whether start with anotherString when transformed to lower case. * * @param thisString * @param anotherString * @return */ public static boolean startWithToLowerCase(String thisString, String anotherString) { AssertUtils.assertNotNull(thisString, "Param must not be null!"); AssertUtils.assertNotNull(anotherString, "Param must not be null!"); if (thisString.length() < anotherString.length()) { return false; } boolean ret; int index = 0; do { if (thisString.charAt(index) > CHAR_Z || thisString.charAt(index) < CHAR_A) { ret = thisString.charAt(index) == anotherString.charAt(index); } else { ret = thisString.charAt(index) + CASE_GAP == anotherString.charAt(index); } } while (ret && ++index < anotherString.length()); return ret; } public static String removeCR(String originalStr) { return removeSpcChar(originalStr, CR); } /** *

Remove Special Characters, Such as \t、\n、\r

* * @param originalStr the String to deal * @param spcChar Special char * @return removed string */ public static String removeSpcChar(String originalStr, String spcChar) { AssertUtils.assertNotNull(spcChar, "SpcChar must not be null!"); if (originalStr == null || originalStr.isEmpty()) { return originalStr; } return originalStr.replaceAll(String.format("[%s]", spcChar), EMPTY_STRING); } } ================================================ FILE: sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/util/ThreadPoolUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.SynchronousQueue; /** * Thread pool utils * * @author qilong.zql * @since 0.4.0 */ public class ThreadPoolUtils { /** * Build Queue * * @param size size of queue * @return queue */ public static BlockingQueue buildQueue(int size) { return buildQueue(size, false); } /** * Build Queue * * @param size size of queue * @param isPriority whether use priority queue or not * @return queue */ public static BlockingQueue buildQueue(int size, boolean isPriority) { BlockingQueue queue; if (size == 0) { queue = new SynchronousQueue<>(); } else { if (isPriority) { queue = size < 0 ? new PriorityBlockingQueue() : new PriorityBlockingQueue(size); } else { queue = size < 0 ? new LinkedBlockingDeque() : new LinkedBlockingDeque(size); } } return queue; } } ================================================ FILE: sofa-ark-parent/core/common/src/main/resources/com/alipay/sofa/ark/log/log4j/log-conf.xml ================================================ ================================================ FILE: sofa-ark-parent/core/common/src/main/resources/com/alipay/sofa/ark/log/log4j2/log-conf.xml ================================================ ${sys:logging.level.com.alipay.sofa.ark} ${sys:logging.path} ${sys:file.encoding} %d %-5p %-32t - %m%n %d %-5p %-32t - %m%n %d %-5p %-32t %c - %m%n ================================================ FILE: sofa-ark-parent/core/common/src/main/resources/com/alipay/sofa/ark/log/logback/log-conf.xml ================================================ true error ACCEPT DENY ${logging.path}/sofa-ark/common-error.log ${logging.path}/sofa-ark/common-error.log.%d{yyyy-MM-dd} 30 %d %-5p %-32t - %m%n ${file.encoding} true ${logging.level.com.alipay.sofa.ark} ACCEPT DENY ${logging.path}/sofa-ark/common-default.log ${logging.path}/sofa-ark/common-default.log.%d{yyyy-MM-dd} 30 %d %-5p %-32t - %m%n ${file.encoding} true ${logging.level.com.alipay.sofa.ark} ACCEPT DENY ${logging.path}/sofa-ark/config-manage.log ${logging.path}/sofa-ark/config-manage.log.%d{yyyy-MM-dd} 30 %d %-5p %-32t - %m%n ${file.encoding} ================================================ FILE: sofa-ark-parent/core/common/src/test/java/com/alipay/sofa/ark/common/adapter/ArkLogbackContextSelectorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.adapter; import ch.qos.logback.classic.ClassicConstants; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.selector.ContextSelector; import ch.qos.logback.classic.util.ContextSelectorStaticBinder; import ch.qos.logback.core.CoreConstants; import org.junit.Assert; import org.junit.Test; import org.slf4j.ILoggerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; /** * @author: yuanyuan * @date: 2023/12/12 5:02 下午 */ public class ArkLogbackContextSelectorTest { @Test public void testContextSelector() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { System.setProperty(ClassicConstants.LOGBACK_CONTEXT_SELECTOR, "com.alipay.sofa.ark.common.adapter.ArkLogbackContextSelector"); Logger logger = LoggerFactory.getLogger(ArkLogbackContextSelectorTest.class); System.clearProperty(ClassicConstants.LOGBACK_CONTEXT_SELECTOR); ILoggerFactory iLoggerFactory = LoggerFactory.getILoggerFactory(); LoggerContext loggerContext = (LoggerContext) iLoggerFactory; ContextSelectorStaticBinder selectorStaticBinder = ContextSelectorStaticBinder .getSingleton(); ContextSelector contextSelector = selectorStaticBinder.getContextSelector(); Assert.assertTrue(contextSelector instanceof ArkLogbackContextSelector); Assert.assertEquals(loggerContext, contextSelector.getDefaultLoggerContext()); URL url = ArkLogbackContextSelectorTest.class.getClassLoader().getResource(""); URLClassLoader loader = new URLClassLoader(new URL[] { url }, null); String contextName = CoreConstants.DEFAULT_CONTEXT_NAME; Method getContext = ArkLogbackContextSelector.class.getDeclaredMethod("getContext", ClassLoader.class); getContext.setAccessible(true); Object invoke = getContext.invoke(contextSelector, loader); Assert.assertNotNull(invoke); Assert.assertEquals(invoke, contextSelector.getLoggerContext(contextName)); Assert.assertTrue(contextSelector.getContextNames().contains(contextName)); } } ================================================ FILE: sofa-ark-parent/core/common/src/test/java/com/alipay/sofa/ark/common/thread/CommonThreadPoolTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.thread; import org.junit.Test; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.PriorityBlockingQueue; import static com.alipay.sofa.ark.common.thread.ThreadPoolManager.*; import static com.alipay.sofa.ark.common.util.ThreadPoolUtils.buildQueue; import static java.lang.Integer.MAX_VALUE; import static org.apache.commons.beanutils.BeanUtils.copyProperties; import static org.junit.Assert.*; public class CommonThreadPoolTest { private CommonThreadPool commonThreadPool; @Test public void testCommonThreadPool() throws Exception { CommonThreadPool commonThreadPool = new CommonThreadPool(); copyProperties(commonThreadPool, commonThreadPool); commonThreadPool.setPrestartAllCoreThreads(true); assertNotNull(commonThreadPool.getExecutor()); registerThreadPool("a", commonThreadPool); assertNotNull(getThreadPool("a")); unRegisterUserThread("a"); assertNull(getThreadPool("a")); } @Test public void testBuildQueue() { BlockingQueue queue = buildQueue(5, true); assertEquals(PriorityBlockingQueue.class, queue.getClass()); assertEquals(MAX_VALUE, queue.remainingCapacity()); queue = buildQueue(-1, true); assertEquals(PriorityBlockingQueue.class, queue.getClass()); assertEquals(0, queue.size()); queue = buildQueue(5, false); assertEquals(LinkedBlockingDeque.class, queue.getClass()); assertEquals(5, queue.remainingCapacity()); queue = buildQueue(-1, false); assertEquals(LinkedBlockingDeque.class, queue.getClass()); assertEquals(0, queue.size()); } } ================================================ FILE: sofa-ark-parent/core/common/src/test/java/com/alipay/sofa/ark/common/util/AssertUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import org.junit.Test; import java.io.File; import static com.alipay.sofa.ark.common.util.AssertUtils.*; import static com.alipay.sofa.ark.common.util.FileUtils.file; import static java.lang.System.getProperty; import static org.junit.Assert.assertTrue; /** * * @author ruoshan * @since 0.1.0 */ public class AssertUtilsTest { public static File getTmpDir() { String tmpPath = getProperty("java.io.tmpdir"); return file(tmpPath); } @Test(expected = IllegalArgumentException.class) public void testAssertNotNullNull() { String msg = "object is null"; try { assertNotNull(null, msg); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); assertTrue(e.getMessage().contains(msg)); throw e; } } @Test public void testAssertNotNullNotNull() { assertNotNull(new Object(), "object is null"); } @Test public void testAssertIsTrue() { isTrue(true, "Exception %s", "error"); try { isTrue(false, "Exception %s", "error"); } catch (IllegalArgumentException ex) { assertTrue("Exception error".equals(ex.getMessage())); } } @Test public void testAssertIsFalse() { isFalse(false, "Exception %s", "error"); try { isFalse(true, "Exception %s", "error"); } catch (IllegalArgumentException ex) { assertTrue("Exception error".equals(ex.getMessage())); } } @Test(expected = IllegalArgumentException.class) public void assertNull() { AssertUtils.assertNull(new Object(), "should be nul!"); } } ================================================ FILE: sofa-ark-parent/core/common/src/test/java/com/alipay/sofa/ark/common/util/BizIdentityUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import com.alipay.sofa.ark.spi.model.Biz; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; import static org.mockito.Mockito.when; /** * @author qilong.zql * @since 0.6.0 */ public class BizIdentityUtilsTest { @Test public void testBizIdentity() { Biz biz = Mockito.mock(Biz.class); when(biz.getBizName()).thenReturn("biz-demo"); when(biz.getBizVersion()).thenReturn("1.0.0"); String bizIdentity = BizIdentityUtils.generateBizIdentity(biz); Assert.assertEquals("biz-demo:1.0.0", bizIdentity); Assert.assertFalse(BizIdentityUtils.isValid(null)); Assert.assertFalse(BizIdentityUtils.isValid("")); Assert.assertFalse(BizIdentityUtils.isValid("name:1.0:1.0")); Assert.assertTrue(BizIdentityUtils.isValid("name:1.0")); } } ================================================ FILE: sofa-ark-parent/core/common/src/test/java/com/alipay/sofa/ark/common/util/ClassLoaderUtilTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import com.alipay.sofa.ark.exception.ArkRuntimeException; import org.junit.Assert; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Collections; import java.util.List; import static com.alipay.sofa.ark.common.util.EnvironmentUtils.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.when; /** * * @author ruoshan * @since 0.1.0 */ public class ClassLoaderUtilTest { private class MockClassLoader extends ClassLoader { private final URLClassPath ucp; private MockClassLoader(URL[] urls) { ucp = new URLClassPath(urls); } private URL[] getURLs() { return ucp.getURLs(); } private class URLClassPath { private ArrayList path = new ArrayList<>(); private URLClassPath(URL[] urls) { Collections.addAll(path, urls); } private URL[] getURLs() { return this.path.toArray(new URL[0]); } } } @Test public void testPushContextClassLoader() { ClassLoader classLoader = new URLClassLoader(new URL[] {}); ClassLoaderUtils.pushContextClassLoader(classLoader); Assert.assertEquals(classLoader, Thread.currentThread().getContextClassLoader()); } @Test public void testPopContextClassLoader() { ClassLoader classLoader = new URLClassLoader(new URL[] {}); ClassLoaderUtils.popContextClassLoader(classLoader); Assert.assertEquals(classLoader, Thread.currentThread().getContextClassLoader()); } @Test @SuppressWarnings({ "restriction", "unchecked" }) public void testGetURLs() { ClassLoader urlClassLoader = new URLClassLoader(new URL[] {}); Assert.assertArrayEquals(((URLClassLoader) urlClassLoader).getURLs(), ClassLoaderUtils.getURLs(urlClassLoader)); ClassLoader appClassLoader = this.getClass().getClassLoader(); URL[] urls = null; if (appClassLoader instanceof URLClassLoader) { urls = ((URLClassLoader) appClassLoader).getURLs(); Assert.assertArrayEquals(urls, ClassLoaderUtils.getURLs(appClassLoader)); } else { String classpath = System.getProperty("java.class.path"); String[] classpathEntries = classpath.split(System.getProperty("path.separator")); List classpathURLs = new ArrayList<>(); for (String classpathEntry : classpathEntries) { URL url = null; try { url = FileUtils.file(classpathEntry).toURI().toURL(); } catch (MalformedURLException e) { e.printStackTrace(); throw new ArkRuntimeException("Failed to get urls from " + appClassLoader, e); } classpathURLs.add(url); } urls = classpathURLs.toArray(new URL[0]); } Assert.assertArrayEquals(urls, ClassLoaderUtils.getURLs(appClassLoader)); URL[] urLs = ClassLoaderUtils.getURLs(null); Assert.assertNotNull(urLs); } @Test public void testGetAgentClassPath() { List mockArguments = new ArrayList<>(); String workingPath = this.getClass().getClassLoader() .getResource("").getPath(); mockArguments.add(String.format("-javaagent:%s", workingPath)); RuntimeMXBean runtimeMXBean = Mockito.mock(RuntimeMXBean.class); when(runtimeMXBean.getInputArguments()).thenReturn(mockArguments); MockedStatic managementFactoryMockedStatic = Mockito.mockStatic(ManagementFactory.class); managementFactoryMockedStatic.when(ManagementFactory::getRuntimeMXBean).thenReturn(runtimeMXBean); URL[] agentUrl = ClassLoaderUtils.getAgentClassPath(); Assert.assertEquals(1, agentUrl.length); managementFactoryMockedStatic.close(); } @Test public void testParseSkyWalkingAgentPath() { List mockArguments = new ArrayList<>(); String workingPath = this.getClass().getClassLoader() .getResource("sample-skywalking-agent.jar").getPath(); mockArguments.add(String.format("-javaagent:%s", workingPath)); RuntimeMXBean runtimeMXBean = Mockito.mock(RuntimeMXBean.class); when(runtimeMXBean.getInputArguments()).thenReturn(mockArguments); MockedStatic managementFactoryMockedStatic = Mockito.mockStatic(ManagementFactory.class); managementFactoryMockedStatic.when(ManagementFactory::getRuntimeMXBean).thenReturn(runtimeMXBean); URL[] agentUrl = ClassLoaderUtils.getAgentClassPath(); Assert.assertEquals(2, agentUrl.length); managementFactoryMockedStatic.close(); } @Test public void testEnvironmentUtils() { assertNull(getProperty("not_exists_prop")); setSystemProperty("not_exists_prop", "aaa"); assertEquals("aaa", getProperty("not_exists_prop")); clearSystemProperty("not_exists_prop"); assertNull(getProperty("not_exists_prop")); } } ================================================ FILE: sofa-ark-parent/core/common/src/test/java/com/alipay/sofa/ark/common/util/ClassUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import org.junit.Test; import java.io.File; import java.util.HashSet; import java.util.Set; import static com.alipay.sofa.ark.common.util.ClassUtils.collectClasses; import static com.alipay.sofa.ark.common.util.ClassUtils.getPackageName; import static com.alipay.sofa.ark.spi.constant.Constants.DEFAULT_PACKAGE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author qilong.zql * @since 0.3.0 */ public class ClassUtilsTest { @Test public void testGetPackageName() { assertEquals("a.b", getPackageName("a.b.C")); assertEquals(DEFAULT_PACKAGE, getPackageName("C")); } @Test public void testCollectClasses() throws Exception { File dir = new File("target/classes"); // fix mvn test fail issues File dir2 = new File(dir.getAbsolutePath()); if (!dir2.exists()) { return; } Set classNames = new HashSet<>(collectClasses(dir2)); assertTrue(classNames.contains("com.alipay.sofa.ark.common.util.ClassUtils")); } } ================================================ FILE: sofa-ark-parent/core/common/src/test/java/com/alipay/sofa/ark/common/util/FileUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.IOException; import java.net.URL; import static com.alipay.sofa.ark.common.util.FileUtils.*; import static java.lang.System.getProperty; import static java.lang.System.setProperty; import static org.apache.commons.io.FileUtils.deleteQuietly; import static org.apache.commons.io.FileUtils.touch; import static org.junit.Assert.*; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/7/28 11:24 PM * @since **/ public class FileUtilsTest { private static final String ORIGIN = getProperty("os.name"); @Before public void before() { setProperty("os.name", "windows"); } @After public void after() { setProperty("os.name", ORIGIN); } @Test public void testGetCompatiblePath() { String winPath = getCompatiblePath("C:\\a\\b\\c"); assertTrue(winPath.contains("/")); String macPath = getCompatiblePath("/a/b/c"); assertTrue(winPath.contains(macPath)); } @Test public void testSHA1Hash() throws IOException { URL url = this.getClass().getResource(this.getClass().getSimpleName() + ".class"); assertNotNull(sha1Hash(file(url.getFile()))); } @Test public void testUnzip() throws IOException { URL sampleBiz = this.getClass().getClassLoader().getResource("sample-biz.jar"); File file = file(sampleBiz.getFile()); assertNotNull(unzip(file, file.getAbsolutePath() + "-unpack")); } @Test public void testMkdir() { assertNull(mkdir("")); // test recursive creation File newDir = mkdir("C:\\a\\b\\c"); assertNotNull(newDir); // test for exist path assertNotNull(mkdir("C:\\a\\b\\c")); // del the dir deleteQuietly(newDir); } @Test public void testDecodePath() { String path = "C:\\temp dir\\b\\c"; String encodedPath = "C:\\temp%20dir\\b\\c"; assertEquals(path, decodePath(path)); assertEquals(path, decodePath(encodedPath)); } @Test public void testNewFile() throws IOException { String dir = "C:\\temp dir\\b\\c"; String encodedPath = "C:\\temp%20dir\\b\\c"; mkdir(dir); touch(new File(dir, "test.txt")); File file = file(encodedPath, "test.txt"); assertNotNull(file); assertTrue(file.exists()); file = new File(encodedPath, "test.txt"); assertFalse(file.exists()); } } ================================================ FILE: sofa-ark-parent/core/common/src/test/java/com/alipay/sofa/ark/common/util/ParseUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import com.alipay.sofa.ark.spi.constant.Constants; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.HashSet; import java.util.Set; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/23 11:18 AM * @since **/ public class ParseUtilsTest { Set candidates = new HashSet<>(); Set resources = new HashSet<>(); Set stems = new HashSet<>(); Set suffixStems = new HashSet<>(); @Before public void before() { candidates.add("spring.xsd"); candidates.add("*.xsd"); candidates.add("spring/*"); candidates.add(Constants.PACKAGE_PREFIX_MARK); } @Test public void testParseUtils() { ParseUtils.parseResourceAndStem(candidates, stems, suffixStems, resources); Assert.assertTrue(stems.size() == 1 && stems.contains("spring/")); Assert.assertTrue(resources.size() == 1 && resources.contains("spring.xsd")); Assert.assertTrue(suffixStems.size() == 1 && suffixStems.contains(".xsd")); } @After public void after() { candidates.clear(); resources.clear(); stems.clear(); suffixStems.clear(); candidates = null; resources = null; suffixStems = null; stems = null; } } ================================================ FILE: sofa-ark-parent/core/common/src/test/java/com/alipay/sofa/ark/common/util/PortSelectUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import org.junit.Test; import java.io.IOException; import java.net.ServerSocket; /** * @author qilong.zql * @since 0.5.0 */ public class PortSelectUtilsTest { @Test public void selectMinimumPort() { int port = PortSelectUtils.selectAvailablePort(1050, 100); AssertUtils.isTrue(port == PortSelectUtils.MIN_PORT_NUMBER, "Select Error Port."); } @Test public void selectUnusedPort() { int port = PortSelectUtils.selectAvailablePort(1234, 100); AssertUtils.isTrue(port == 1234, "Select Error Port."); } @Test public void selectUsedPort() { try (ServerSocket ss = new ServerSocket(1234)) { int port = PortSelectUtils.selectAvailablePort(1234, 100); AssertUtils.isTrue(port == 1235, "Select Error Port."); } catch (IOException ex) { // ignore } } @Test public void selectSinglePort() { try (ServerSocket ss = new ServerSocket(1234)) { int port = PortSelectUtils.selectAvailablePort(1234, 1); AssertUtils.isTrue(port == -1, "Select Error Port."); } catch (IOException ex) { // ignore } } @Test public void selectMaximumPort() { int port = PortSelectUtils.selectAvailablePort(65555, 10); AssertUtils.isTrue(port == -1, "Select Error Port."); } } ================================================ FILE: sofa-ark-parent/core/common/src/test/java/com/alipay/sofa/ark/common/util/SimpleByteBufferTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import org.junit.Test; import static org.junit.Assert.assertEquals; public class SimpleByteBufferTest { private SimpleByteBuffer simpleByteBuffer = new SimpleByteBuffer(); @Test public void testSimpleByteBuffer() { simpleByteBuffer.backSpace(); simpleByteBuffer.delete(); assertEquals(-1, simpleByteBuffer.goRight()); assertEquals(false, simpleByteBuffer.goLeft()); for (int i = 0; i <= 20; i++) { simpleByteBuffer.add((byte) i); } assertEquals(0, simpleByteBuffer.goRight()); assertEquals(1, simpleByteBuffer.goRight()); assertEquals(true, simpleByteBuffer.goLeft()); for (int i = 0; i <= 40; i++) { simpleByteBuffer.insert((byte) i); } assertEquals(1, simpleByteBuffer.goRight()); assertEquals(2, simpleByteBuffer.goRight()); assertEquals(true, simpleByteBuffer.goLeft()); simpleByteBuffer.backSpace(); assertEquals(2, simpleByteBuffer.goRight()); simpleByteBuffer.delete(); assertEquals(4, simpleByteBuffer.goRight()); assertEquals(60, simpleByteBuffer.getBuffer().length); assertEquals(60, simpleByteBuffer.getSize()); assertEquals(44, simpleByteBuffer.getPos()); assertEquals(16, simpleByteBuffer.getGap()); assertEquals(60, simpleByteBuffer.getAndClearBuffer().length); assertEquals(0, simpleByteBuffer.getSize()); assertEquals(0, simpleByteBuffer.getPos()); } } ================================================ FILE: sofa-ark-parent/core/common/src/test/java/com/alipay/sofa/ark/common/util/StringUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.common.util; import org.junit.Assert; import org.junit.Test; import java.util.*; /** * @author qilong.zql * @since 0.1.0 */ public class StringUtilsTest { @Test public void testEmpty() { Assert.assertTrue(StringUtils.isEmpty("")); Assert.assertTrue(StringUtils.isEmpty(null)); Assert.assertFalse(StringUtils.isEmpty("aaa")); } @Test public void testSameStr() { Assert.assertTrue(StringUtils.isSameStr(null, null)); Assert.assertFalse(StringUtils.isSameStr(null, "")); Assert.assertFalse(StringUtils.isSameStr(null, "a")); Assert.assertFalse(StringUtils.isSameStr("aa", null)); Assert.assertFalse(StringUtils.isSameStr("aa", "")); Assert.assertFalse(StringUtils.isSameStr("aa", "a")); Assert.assertTrue(StringUtils.isSameStr("aa", "aa")); } @Test @SuppressWarnings("unchecked") public void testListToStr() { Assert.assertTrue("".equals(StringUtils.setToStr(null, ","))); Assert.assertTrue("".equals(StringUtils.setToStr(Collections. emptySet(), ","))); Assert.assertTrue("ast".equals(StringUtils.setToStr(Collections.singleton("ast"), "&&"))); LinkedHashSet linkedHashSet = new LinkedHashSet(); linkedHashSet.add("ab"); linkedHashSet.add(" ba "); Assert.assertTrue("ab,ba".equals(StringUtils.setToStr(linkedHashSet, ","))); linkedHashSet.add("cb"); Assert.assertTrue("ab&&ba&&cb".equals(StringUtils.setToStr(linkedHashSet, "&&"))); Assert.assertTrue("abbacb".equals(StringUtils.setToStr(linkedHashSet, ""))); } @Test public void testContains() { String a = "This is my test source string"; Assert.assertTrue(StringUtils.contains(a, "my")); Assert.assertTrue(StringUtils.contains(a, "")); Assert.assertFalse(StringUtils.contains(a, null)); Assert.assertFalse(StringUtils.contains(null, null)); Assert.assertFalse(StringUtils.contains(null, "my")); } @Test public void testStrToList() { List list = StringUtils.strToList("ab,ba,cb", ","); Assert.assertTrue(list.size() == 3); Assert.assertTrue(list.get(0).equals("ab")); Assert.assertTrue(list.get(1).equals("ba")); Assert.assertTrue(list.get(2).equals("cb")); } @Test public void testStrToSet() { Set set = StringUtils.strToSet(null, "&&"); Assert.assertTrue(set.isEmpty()); set = StringUtils.strToSet("ab&&ba&&cb&&cb", "&&"); Assert.assertTrue(set.size() == 3); Object[] array = set.toArray(); Assert.assertTrue(array[0].equals("ab")); Assert.assertTrue(array[1].equals("ba")); Assert.assertTrue(array[2].equals("cb")); } @Test public void testStartWithToLowerCase() { Assert.assertFalse(StringUtils.startWithToLowerCase("ab", "abc")); Assert.assertTrue(StringUtils.startWithToLowerCase("AbC", "abc")); Assert.assertTrue(StringUtils.startWithToLowerCase("aB#", "ab#")); Assert.assertTrue(StringUtils.startWithToLowerCase("~Ab", "~ab")); } @Test public void testRemoveCR() { Assert.assertNull(StringUtils.removeCR(null)); Assert.assertEquals("", StringUtils.removeCR("")); Assert.assertEquals("com.alipay.sofa:biz-child1-child1:1.0.0:jar:test\n", StringUtils.removeCR("com.alipay.sofa:biz-child1-child1:1.0.0:jar:test\r\n")); } @Test public void testRemoveSpcChar() { Assert.assertThrows(IllegalArgumentException.class, () -> { StringUtils.removeSpcChar("", null); }); // 顺序无关 Assert.assertEquals("com.alipay.sofa:biz-child1-child1:1.0.0:jar:test", StringUtils.removeSpcChar("com.alipay.sofa:biz-child1-child1:1.0.0:jar:test\r\n\t", "\t\n\r")); Assert.assertEquals("com.alipay.sofa:biz-child1-child1:1.0.0:jar:test", StringUtils.removeSpcChar("com.alipay.sofa:biz-child1-child1:1.0.0:jar:test\r\n", "\r\n")); Assert.assertEquals("com.alipay.sofa:biz-child1-child1:1.0.0:jar:test\r", StringUtils.removeSpcChar("com.alipay.sofa:biz-child1-child1:1.0.0:jar:test\r\n", "\n")); } } ================================================ FILE: sofa-ark-parent/core/exception/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-exception` **Package**: `com.alipay.sofa.ark.exception` This module defines all exception types used in the SOFAArk project. ## Purpose - Centralized exception definitions - Consistent error handling across the project ## Key Classes ### `ArkException` Base exception class for SOFAArk. Extends `RuntimeException`. ### `ArkRuntimeException` Runtime exception for non-recoverable errors during Ark container operation. ## Usage Throw these exceptions when: - Ark container fails to start - Plugin or Biz fails to load/start - Configuration errors - ClassLoader errors ```java throw new ArkRuntimeException("Failed to start Ark container"); ``` ## Used By All SOFAArk modules use these exception types for consistent error handling. ================================================ FILE: sofa-ark-parent/core/exception/pom.xml ================================================ 4.0.0 sofa-ark-core com.alipay.sofa ${sofa.ark.version} sofa-ark-exception ${project.groupId}:${project.artifactId} ================================================ FILE: sofa-ark-parent/core/exception/src/main/java/com/alipay/sofa/ark/exception/ArkLoaderException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.exception; /** * SOFAArk ClassLoader Exception * * @author ruoshan * @since 0.1.0 */ public class ArkLoaderException extends ClassNotFoundException { private Throwable cause; public ArkLoaderException(String s) { super(s); } public ArkLoaderException(String s, Throwable ex) { super(s); this.cause = ex; } @Override public Throwable fillInStackTrace() { // dont fill stack trace, avoid cpu hotspot return this; } public Throwable getCause() { return cause; } } ================================================ FILE: sofa-ark-parent/core/exception/src/main/java/com/alipay/sofa/ark/exception/ArkRuntimeException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.exception; /** * SOFAArk Runtime Exception * * @author ruoshan * @since 0.1.0 */ public class ArkRuntimeException extends RuntimeException { public ArkRuntimeException(String message, Throwable cause) { super(message, cause); } public ArkRuntimeException(Throwable cause) { super(cause); } public ArkRuntimeException(String message) { super(message); } } ================================================ FILE: sofa-ark-parent/core/pom.xml ================================================ 4.0.0 sofa-ark-parent com.alipay.sofa ${sofa.ark.version} sofa-ark-core ${project.groupId}:${project.artifactId} pom spi common exception api ================================================ FILE: sofa-ark-parent/core/spi/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-spi` **Package**: `com.alipay.sofa.ark.spi` This module defines the Service Provider Interfaces (SPI) for SOFAArk. It contains all the core interfaces, models, and extension points that allow customization and integration with the Ark container. ## Purpose - Define core interfaces for Ark container services - Define data models for Biz, Plugin, and Archive - Provide extension points for custom implementations - Define event system for lifecycle notifications ## Key Packages ### `spi.model` Core data models: - `Biz` / `BizInfo` / `BizState` / `BizConfig` / `BizOperation` - Business module model - `Plugin` / `PluginContext` / `PluginConfig` / `PluginOperation` - Plugin model ### `spi.service` Service interfaces: - `biz.BizManagerService` - Manage business modules lifecycle - `biz.BizFactoryService` - Create business module instances - `plugin.PluginManagerService` - Manage plugins - `plugin.PluginFactoryService` - Create plugin instances - `classloader.ClassLoaderService` - Manage classloaders - `event.EventAdminService` - Event publishing/subscribing - `injection.InjectionService` - Dependency injection - `extension.ArkServiceLoader` - SPI extension loading ### `spi.archive` Archive interfaces: - `Archive` - Base archive interface - `BizArchive` - Business module archive - `PluginArchive` - Plugin archive - `ExecutableArchive` - Executable Ark archive - `ContainerArchive` - Container archive ### `spi.pipeline` Startup pipeline interfaces: - `Pipeline` - Pipeline that processes startup stages - `PipelineStage` - Individual stage in the startup process - `PipelineContext` - Context passed between stages ### `spi.event` Event types for lifecycle notifications: - `biz.*` - Business module events (Before/After Install, Uninstall, Start, Stop, Switch) - `plugin.*` - Plugin events ### `spi.constant.Constants` Constant definitions used throughout SOFAArk. ## Extension Points Implement these interfaces to extend SOFAArk: 1. `PipelineStage` - Add custom startup stages 2. `PluginActivator` - Custom plugin activation logic 3. `AddBizToStaticDeployHook` - Hook for adding biz during static deployment 4. `EmbeddedServerService` - Custom embedded server implementation ## Dependencies - `sofa-ark-exception` - Exception definitions ## Used By - `sofa-ark-api` - API layer sits on top of SPI - `sofa-ark-container` - Implements all SPI services - `sofa-ark-archive` - Implements archive interfaces - All other SOFAArk modules depend on SPI ================================================ FILE: sofa-ark-parent/core/spi/pom.xml ================================================ 4.0.0 sofa-ark-core com.alipay.sofa ${sofa.ark.version} sofa-ark-spi ${project.groupId}:${project.artifactId} com.alipay.sofa sofa-ark-exception junit junit test org.springframework spring-beans 5.3.29 test ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/archive/AbstractArchive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.archive; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * implementation of {@link ContainerArchive}, {@link PluginArchive}, {@link BizArchive} * should extends this * * @author qilong.zql * @since 0.1.0 */ public abstract class AbstractArchive implements Archive { @SuppressWarnings("unchecked") @Override public List getNestedArchives(EntryFilter filter) throws IOException { List nestedArchives = new ArrayList(); for (Entry entry : this) { if (filter.matches(entry)) { nestedArchives.add(getNestedArchive(entry)); } } return Collections.unmodifiableList(nestedArchives); } public URL[] getUrls(EntryFilter entryFilter) throws IOException { List archives = getNestedArchives(entryFilter); List urls = new ArrayList<>(archives.size() + 1); urls.add(getUrl()); for (Archive archive : archives) { urls.add(archive.getUrl()); } return urls.toArray(new URL[urls.size()]); } public boolean isEntryExist(EntryFilter filter) { for (Entry entry : this) { if (filter.matches(entry)) { return true; } } return false; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/archive/Archive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.archive; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import java.util.jar.Manifest; import java.util.zip.ZipEntry; /** * An archive that can be parsed in uniform style * * @author Phillip Webb */ public interface Archive extends Iterable { /** * Returns a URL that can be used to load the archive. * @return the archive URL * @throws MalformedURLException if the URL is malformed */ URL getUrl() throws MalformedURLException; /** * Returns the manifest of the archive. * @return the manifest * @throws IOException if the manifest cannot be read */ Manifest getManifest() throws IOException; /** * Returns nested {@link Archive}s for entries that match the specified filter. * @param filter the filter used to limit entries * @return nested archives * @throws IOException if nested archives cannot be read */ List getNestedArchives(EntryFilter filter) throws IOException; /** * getNestedArchive * @param entry * @return * @throws IOException */ Archive getNestedArchive(Entry entry) throws IOException; /** * getInputStream * @param zipEntry * @return * @throws IOException */ InputStream getInputStream(ZipEntry zipEntry) throws IOException; /** * Represents a single entry in the archive. */ interface Entry { /** * Returns {@code true} if the entry represents a directory. * @return if the entry is a directory */ boolean isDirectory(); /** * Returns the name of the entry. * @return the name of the entry */ String getName(); } /** * Strategy interface to filter {@link Entry Entries}. */ interface EntryFilter { /** * Apply the jar entry filter. * @param entry the entry to filter * @return {@code true} if the filter matches */ boolean matches(Entry entry); } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/archive/BizArchive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.archive; import java.io.IOException; import java.net.URL; /** * An archive represent an ark-biz-module * * @author qilong.zql * @since 0.1.0 */ public interface BizArchive extends Archive { /** * fetch classpath of archive to startup ark-biz module * * @return the classpath contained in ark-biz archive * @throws IOException throw io exception when get biz classpath */ URL[] getUrls() throws IOException; /** * check whether the entry satisfy the given {@link com.alipay.sofa.ark.spi.archive.Archive.EntryFilter} * exists or not * * @param filter * @return */ boolean isEntryExist(EntryFilter filter); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/archive/ContainerArchive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.archive; import java.io.IOException; import java.net.URL; /** * An archive represents an ark-container * * @author qilong.zql * @since 0.1.0 */ public interface ContainerArchive extends Archive { /** * fetch classpath of archive to startup ark-container * * @return the classpath contained in ark-container archive * @throws IOException throw exception when meets error */ URL[] getUrls() throws IOException; } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/archive/ExecutableArchive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.archive; import java.net.URL; import java.util.List; /** * An archive represents an executable fat jar with sofa-ark * * @author qilong.zql * @since 0.1.0 */ public interface ExecutableArchive extends Archive { /** * Get Ark Container Archiver * @return * @throws Exception throw exception when meets error */ ContainerArchive getContainerArchive() throws Exception; /** * Get all Biz Archiver * @return * @throws Exception throw exception when meets error */ List getBizArchives() throws Exception; /** * Get all Plugin Archiver * @return * @throws Exception throw exception when meets error */ List getPluginArchives() throws Exception; /** * Get ark conf class path * * @return return ark conf class path * @throws Exception throw exception when meets error */ List getConfClasspath() throws Exception; } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/archive/PluginArchive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.archive; import java.io.IOException; import java.net.URL; import java.util.Set; /** * An archive represents an ark-plugin * * @author qilong.zql * @since 0.1.0 */ public interface PluginArchive extends Archive { /** * fetch classpath of archive to startup ark-plugin * * @return the classpath contained in ark-plugin archive * @throws IOException throw exception when meets error */ URL[] getUrls() throws IOException; /** * setExtensionUrls * @param extensionUrls */ void setExtensionUrls(URL[] extensionUrls); /** * check whether the entry satisfy the given {@link com.alipay.sofa.ark.spi.archive.Archive.EntryFilter} * exists or not * * @param filter * @return */ boolean isEntryExist(EntryFilter filter); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/argument/CommandArgument.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.argument; /** * SOFAArk command-line arguments * * @author qilong.zql * @since 0.1.0 */ public interface CommandArgument { /** * command-line arguments received by ark container. * pattern: -A[key]=[value] */ String ARK_CONTAINER_ARGUMENTS_MARK = "-A"; String CLASSPATH_ARGUMENT_KEY = "classpath"; String FAT_JAR_ARGUMENT_KEY = "jar"; String CLASSPATH_SPLIT = ","; String PROFILE = "profile"; String VM_PROFILE = "ark.profile"; String PROFILE_SPLIT = ","; /** * command-line arguments received by bootstrap ark biz when execute in IDE. * pattern: -B[key]=[value] */ String ARK_BIZ_ARGUMENTS_MARK = "-B"; String ENTRY_CLASS_NAME_ARGUMENT_KEY = "className"; String ENTRY_METHOD_NAME_ARGUMENT_KEY = "methodName"; } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/argument/LaunchCommand.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.argument; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import static com.alipay.sofa.ark.spi.argument.CommandArgument.ARK_BIZ_ARGUMENTS_MARK; import static com.alipay.sofa.ark.spi.argument.CommandArgument.ARK_CONTAINER_ARGUMENTS_MARK; import static com.alipay.sofa.ark.spi.argument.CommandArgument.CLASSPATH_ARGUMENT_KEY; import static com.alipay.sofa.ark.spi.argument.CommandArgument.CLASSPATH_SPLIT; import static com.alipay.sofa.ark.spi.argument.CommandArgument.ENTRY_CLASS_NAME_ARGUMENT_KEY; import static com.alipay.sofa.ark.spi.argument.CommandArgument.ENTRY_METHOD_NAME_ARGUMENT_KEY; import static com.alipay.sofa.ark.spi.argument.CommandArgument.FAT_JAR_ARGUMENT_KEY; import static com.alipay.sofa.ark.spi.argument.CommandArgument.PROFILE; import static com.alipay.sofa.ark.spi.argument.CommandArgument.PROFILE_SPLIT; import static com.alipay.sofa.ark.spi.argument.CommandArgument.VM_PROFILE; import static com.alipay.sofa.ark.spi.constant.Constants.DEFAULT_PROFILE; /** * command argument parsed as a launchCommand * * @author qilong.zql * @since 0.1.0 */ public class LaunchCommand { private URL executableArkBizJar; private URL[] classpath; /** * the following two configs are mainly used by bootstrap ark biz at startup of IDE. */ private String entryClassName; private String entryMethodName; private String[] launchArgs; private String[] profiles; public boolean isExecutedByCommandLine() { return executableArkBizJar != null; } public URL getExecutableArkBizJar() { return executableArkBizJar; } public LaunchCommand setExecutableArkBizJar(URL executableArkBizJar) { this.executableArkBizJar = executableArkBizJar; return this; } public URL[] getClasspath() { return classpath; } public LaunchCommand setClasspath(URL[] classpath) { this.classpath = classpath; return this; } public String getEntryMethodName() { return entryMethodName; } public LaunchCommand setEntryMethodName(String entryMethodName) { this.entryMethodName = entryMethodName; return this; } public String getEntryClassName() { return entryClassName; } public LaunchCommand setEntryClassName(String entryClassName) { this.entryClassName = entryClassName; return this; } public String[] getLaunchArgs() { return launchArgs; } public LaunchCommand setLaunchArgs(String[] launchArgs) { this.launchArgs = launchArgs; return this; } public String[] getProfiles() { if (profiles != null) { return profiles; } String profileVMArgs = System.getProperty(VM_PROFILE); return profileVMArgs == null ? new String[] { DEFAULT_PROFILE } : profileVMArgs .split(PROFILE_SPLIT); } public LaunchCommand setProfiles(String[] profiles) { this.profiles = profiles; return this; } public static LaunchCommand parse(String[] args) throws MalformedURLException { LaunchCommand launchCommand = new LaunchCommand(); String arkJarPrefix = String.format("%s%s=", ARK_CONTAINER_ARGUMENTS_MARK, FAT_JAR_ARGUMENT_KEY); String arkClasspathPrefix = String.format("%s%s=", ARK_CONTAINER_ARGUMENTS_MARK, CLASSPATH_ARGUMENT_KEY); String entryClassNamePrefix = String.format("%s%s=", ARK_BIZ_ARGUMENTS_MARK, ENTRY_CLASS_NAME_ARGUMENT_KEY); String entryMethodNamePrefix = String.format("%s%s=", ARK_BIZ_ARGUMENTS_MARK, ENTRY_METHOD_NAME_ARGUMENT_KEY); String arkConfigProfilePrefix = String.format("%s%s=", ARK_CONTAINER_ARGUMENTS_MARK, PROFILE); List arguments = new ArrayList<>(); for (String arg : args) { arg = arg.trim(); if (arg.startsWith(arkJarPrefix)) { String fatJarUrl = arg.substring(arkJarPrefix.length()); launchCommand.setExecutableArkBizJar(new URL(fatJarUrl)); } else if (arg.startsWith(entryClassNamePrefix)) { String entryClassName = arg.substring(entryClassNamePrefix.length()); launchCommand.setEntryClassName(entryClassName); } else if (arg.startsWith(entryMethodNamePrefix)) { String entryMethodName = arg.substring(entryMethodNamePrefix.length()); launchCommand.setEntryMethodName(entryMethodName); } else if (arg.startsWith(arkClasspathPrefix)) { String classpath = arg.substring(arkClasspathPrefix.length()); List urlList = new ArrayList<>(); for (String url : classpath.split(CLASSPATH_SPLIT)) { if (url.isEmpty()) { continue; } urlList.add(new URL(url)); } launchCommand.setClasspath(urlList.toArray(new URL[urlList.size()])); } else if (arg.startsWith(arkConfigProfilePrefix)) { String profile = arg.substring(arkConfigProfilePrefix.length()); launchCommand.setProfiles(profile.split(PROFILE_SPLIT)); } else { // -A and -B argument would not passed into biz main method. arguments.add(arg); } } return launchCommand.setLaunchArgs(arguments.toArray(new String[] {})); } public static String toString(String[] args) { StringBuilder sb = new StringBuilder(); for (String arg : args) { sb.append(arg); } return sb.toString(); } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/command/Command.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.command; /** * @author qilong.zql * @since 0.5.0 */ public interface Command { /** * Get Command Prefix Marker. * @return */ String getCommandMarker(); /** * Get Command Help message, usually command schema. * @return */ String getCommandHelp(); /** * Process Command * @return * @throws Throwable */ String process() throws Throwable; } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/constant/Constants.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.constant; import java.util.ArrayList; import java.util.List; /** * @author qilong.zql * @since 0.1.0 */ public class Constants { /** * String Constants */ public final static String SPACE_SPLIT = "\\s+"; public final static String STRING_COLON = ":"; public final static String STRING_SEMICOLON = ";"; public final static String TELNET_STRING_END = new String( new byte[] { (byte) 13, (byte) 10 }); public final static String COMMA_SPLIT = ","; public final static String EMPTY_STR = ""; public final static String AMPERSAND_SPLIT = "&"; public final static String EQUAL_SPLIT = "="; public final static String QUESTION_MARK_SPLIT = "?"; public final static String ROOT_WEB_CONTEXT_PATH = "/"; /** * ark conf */ public final static String CONF_BASE_DIR = "conf/"; public final static String ARK_CONF_BASE_DIR = "conf/ark"; public final static String ARK_CONF_FILE = "bootstrap.properties"; public final static String ARK_CONF_FILE_FORMAT = "bootstrap-%s.properties"; public final static String ARK_CONF_YAML_FILE = "bootstrap.yml"; public final static String PLUGIN_EXTENSION_FORMAT = "PLUGIN-EXPORT[%s]"; public final static String DEFAULT_PROFILE = EMPTY_STR; public final static String LOCAL_HOST = "localhost"; /** * ark classloader cache conf */ public final static String ARK_CLASSLOADER_CACHE_CLASS_SIZE_INITIAL = "ark.classloader.cache.class.size.initial"; public final static String ARK_CLASSLOADER_CACHE_CLASS_SIZE_MAX = "ark.classloader.cache.class.size.max"; public final static String ARK_CLASSLOADER_CACHE_CONCURRENCY_LEVEL = "ark.classloader.cache.concurrencylevel"; /** * plugin conf, multi value is split by comma. */ public final static String PLUGIN_ACTIVE_INCLUDE = "ark.plugin.active.include"; public final static String PLUGIN_ACTIVE_EXCLUDE = "ark.plugin.active.exclude"; /** * biz conf, multi value is split by comma. */ public final static String BIZ_ACTIVE_INCLUDE = "ark.biz.active.include"; public final static String BIZ_ACTIVE_EXCLUDE = "ark.biz.active.exclude"; /** * Archiver Marker */ public final static String ARK_CONTAINER_MARK_ENTRY = "com/alipay/sofa/ark/container/mark"; public final static String ARK_PLUGIN_MARK_ENTRY = "com/alipay/sofa/ark/plugin/mark"; public final static String ARK_BIZ_MARK_ENTRY = "com/alipay/sofa/ark/biz/mark"; /** * Ark Plugin Attribute */ public final static String PRIORITY_ATTRIBUTE = "priority"; public final static String GROUP_ID_ATTRIBUTE = "groupId"; public final static String ARTIFACT_ID_ATTRIBUTE = "artifactId"; public final static String PLUGIN_NAME_ATTRIBUTE = "pluginName"; public final static String PLUGIN_VERSION_ATTRIBUTE = "version"; public final static String ACTIVATOR_ATTRIBUTE = "activator"; public final static String WEB_CONTEXT_PATH = "web-context-path"; public final static String IMPORT_CLASSES_ATTRIBUTE = "import-classes"; public final static String IMPORT_PACKAGES_ATTRIBUTE = "import-packages"; public final static String EXPORT_MODE = "export-mode"; public final static String EXPORT_CLASSES_ATTRIBUTE = "export-classes"; public final static String EXPORT_PACKAGES_ATTRIBUTE = "export-packages"; /** * Ark Biz Attribute */ public final static String MAIN_CLASS_ATTRIBUTE = "Main-Class"; public final static String START_CLASS_ATTRIBUTE = "Start-Class"; public final static String ARK_BIZ_NAME = "Ark-Biz-Name"; public final static String ARK_BIZ_VERSION = "Ark-Biz-Version"; public final static String DENY_IMPORT_CLASSES = "deny-import-classes"; public final static String DENY_IMPORT_PACKAGES = "deny-import-packages"; public final static String DENY_IMPORT_RESOURCES = "deny-import-resources"; public final static String INJECT_PLUGIN_DEPENDENCIES = "inject-plugin-dependencies"; public final static String INJECT_EXPORT_PACKAGES = "inject-export-packages"; public final static String DECLARED_LIBRARIES = "declared-libraries"; public final static String DEPENDENT_PLUGINS = "dependent-plugins"; public static final String BRANCH = "commit-branch"; public static final String COMMIT_ID = "commit-id"; public static final String BUILD_USER = "build-user"; public static final String BUILD_EMAIL = "build-email"; public static final String BUILD_TIME = "build-time"; public static final String COMMIT_AUTHOR_NAME = "commit-user-name"; public static final String COMMIT_AUTHOR_EMAIL = "commit-user-email"; public static final String COMMIT_TIMESTAMP = "commit-timestamp"; public static final String COMMIT_TIME = "commit-time"; public static final String REMOTE_ORIGIN_URL = "remote-origin-url"; public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; public final static String PACKAGE_PREFIX_MARK = "*"; public final static String PACKAGE_PREFIX_MARK_2 = ".*"; public final static String DEFAULT_PACKAGE = "."; public final static String MANIFEST_VALUE_SPLIT = COMMA_SPLIT; public final static String RESOURCE_STEM_MARK = "*"; public final static String IMPORT_RESOURCES_ATTRIBUTE = "import-resources"; public final static String EXPORT_RESOURCES_ATTRIBUTE = "export-resources"; public final static String SUREFIRE_BOOT_CLASSPATH = "Class-Path"; public final static String SUREFIRE_BOOT_CLASSPATH_SPLIT = " "; public final static String SUREFIRE_BOOT_JAR = "surefirebooter"; /** * Telnet Server */ public final static String TELNET_SERVER_ENABLE = "sofa.ark.telnet.server.enable"; public final static String TELNET_SERVER_SECURITY_ENABLE = "sofa.ark.telnet.security.enable"; public final static String CONFIG_SERVER_ENABLE = "sofa.ark.config.server.enable"; /** * 配置中心支持, 默认使用zookeeper * value值为com.alipay.sofa.ark.config.ConfigTypeEnum枚举的name() */ public final static String CONFIG_SERVER_TYPE = "sofa.ark.config.server.type"; /** * 使用apollo的namespace */ public final static String CONFIG_APOLLO_NAMESPACE = "sofa-ark"; /** * apollo的namespace下动态命名对应的key */ public final static String APOLLO_MASTER_BIZ_KEY = "masterBiz"; public final static String TELNET_PORT_ATTRIBUTE = "sofa.ark.telnet.port"; public final static int DEFAULT_TELNET_PORT = 1234; public final static int DEFAULT_SELECT_PORT_SIZE = 100; public final static String TELNET_SERVER_WORKER_THREAD_POOL_NAME = "telnet-server-worker"; public final static String TELNET_SESSION_PROMPT = "sofa-ark>"; public final static String TELNET_COMMAND_THREAD_POOL_NAME = "telnet-command"; /** * Event */ public final static String BIZ_EVENT_TOPIC_AFTER_INVOKE_ALL_BIZ_START = "AFTER-INVOKE-ALL-BIZ-START"; public final static String BIZ_EVENT_TOPIC_AFTER_INVOKE_BIZ_START = "AFTER-INVOKE-BIZ-START"; public final static String BIZ_EVENT_TOPIC_AFTER_BIZ_FAILED = "BIZ_EVENT_TOPIC_AFTER_BIZ_FAILED"; public final static String BIZ_EVENT_TOPIC_AFTER_INVOKE_BIZ_STOP = "AFTER-INVOKE-BIZ-STOP"; public final static String BIZ_EVENT_TOPIC_AFTER_INVOKE_BIZ_STOP_FAILED = "AFTER-INVOKE-BIZ-STOP-FAILED"; public final static String BIZ_EVENT_TOPIC_BEFORE_RECYCLE_BIZ = "BEFORE-RECYCLE-BIZ"; public final static String BIZ_EVENT_TOPIC_BEFORE_INVOKE_BIZ_START = "BEFORE-INVOKE-BIZ-START"; public final static String BIZ_EVENT_TOPIC_BEFORE_INVOKE_BIZ_STOP = "BEFORE-INVOKE-BIZ-STOP"; public final static String PLUGIN_EVENT_TOPIC_AFTER_INVOKE_PLUGIN_START = "AFTER-INVOKE-PLUGIN-START"; public final static String PLUGIN_EVENT_TOPIC_AFTER_INVOKE_PLUGIN_STOP = "AFTER-INVOKE-PLUGIN-STOP"; public final static String PLUGIN_EVENT_TOPIC_BEFORE_INVOKE_PLUGIN_START = "BEFORE-INVOKE-PLUGIN-START"; public final static String PLUGIN_EVENT_TOPIC_BEFORE_INVOKE_PLUGIN_STOP = "BEFORE-INVOKE-PLUGIN-STOP"; public final static String BIZ_EVENT_TOPIC_BEFORE_INVOKE_BIZ_SWITCH = "BEFORE-INVOKE-BIZ-SWITCH"; public final static String BIZ_EVENT_TOPIC_AFTER_INVOKE_BIZ_SWITCH = "AFTER-INVOKE-BIZ-SWITCH"; public final static String ARK_EVENT_TOPIC_AFTER_FINISH_STARTUP_STAGE = "AFTER-FINISH-STARTUP-STAGE"; public final static String ARK_EVENT_TOPIC_AFTER_FINISH_DEPLOY_STAGE = "AFTER-FINISH-DEPLOY-STAGE"; /** * Environment Properties */ public final static String SPRING_BOOT_ENDPOINTS_JMX_ENABLED = "endpoints.jmx.enabled"; public final static String LOG4J_IGNORE_TCL = "log4j.ignoreTCL"; public final static String RESOLVE_PARENT_CONTEXT_SERIALIZER_FACTORY = "hessian.parent.context.create"; public final static String EMBED_ENABLE = "sofa.ark.embed.enable"; public final static String PLUGIN_EXPORT_CLASS_ENABLE = "sofa.ark.plugin.export.class.enable"; public final static String EMBED_STATIC_BIZ_ENABLE = "sofa.ark.embed.static.biz.enable"; public final static String EMBED_STATIC_BIZ_IN_RESOURCE_ENABLE = "sofa.ark.embed.static.biz.in.resource.enable"; public final static String ACTIVATE_NEW_MODULE = "activate.new.module"; public final static String BIZ_MAIN_CLASS = "sofa.ark.biz.main.class"; public final static String PLUGIN_CLASS_ISOLATION_ENABLE = "sofa.ark.plugin.class.isolation.enable"; public final static String BIZ_SPECIFY_DEPENDENT_PLUGINS_ENABLE = "sofa.ark.biz.specify.dependent.plugins.enable"; /** * uninstall the biz if it starts up failed */ public final static String AUTO_UNINSTALL_WHEN_FAILED_ENABLE = "sofa.ark.auto.uninstall.when.failed.enable"; /** * unpack the biz when install */ public final static String UNPACK_BIZ_WHEN_INSTALL = "sofa.ark.unpack.biz.when.install"; /** * support multiple version biz as activated */ public final static String ACTIVATE_MULTI_BIZ_VERSION_ENABLE = "sofa.ark.activate.multi.biz.version.enable"; /** * auto remove the biz instance in BizManagerService if it stops failed */ public final static String REMOVE_BIZ_INSTANCE_AFTER_STOP_FAILED = "sofa.ark.remove.biz.instance.when.stop.failed.enable"; /** * Command Provider */ public final static String PLUGIN_COMMAND_UNIQUE_ID = "plugin-command-provider"; public final static String BIZ_COMMAND_UNIQUE_ID = "biz-command-provider"; /** * Ark SPI extension */ public final static String EXTENSION_FILE_DIR = "META-INF/services/sofa-ark/"; public final static String PLUGIN_CLASS_LOADER_HOOK = "plugin-classloader-hook"; public final static String BIZ_CLASS_LOADER_HOOK = "biz-classloader-hook"; public final static String BIZ_CLASS_LOADER_HOOK_DIR = "com.alipay.sofa.ark.biz.classloader.hook.dir"; public final static String BIZ_TEMP_WORK_DIR_RECYCLE_FILE_SUFFIX = "deleted"; /** * Multiply biz name */ public final static String MASTER_BIZ = "com.alipay.sofa.ark.master.biz"; public final static String SOFA_ARK_MODULE = "SOFA-ARK/biz/"; /** * Config Server */ public final static String CONFIG_SERVER_ADDRESS = "com.alipay.sofa.ark.config.address"; public final static String CONFIG_SERVER_ENVIRONMENT = "com.alipay.sofa.ark.config.env"; public final static String CONFIG_PROTOCOL_ZOOKEEPER = "zookeeper"; public final static String CONFIG_PROTOCOL_ZOOKEEPER_HEADER = "zookeeper://"; public final static String ZOOKEEPER_CONTEXT_SPLIT = "/"; public final static String CONFIG_INSTALL_BIZ_DIR = "com.alipay.sofa.ark.biz.dir"; public final static String CONFIG_INSTALL_PLUGIN_DIR = "com.alipay.sofa.ark.plugin.dir"; public final static String CONFIG_BIZ_URL = "bizUrl"; public final static String BIZ_EXTENSION_URLS = "bizExtensionUrls"; public final static String CONFIG_CONNECT_TIMEOUT = "com.alipay.sofa.ark.config.connect.timeout"; public final static int DEFAULT_CONFIG_CONNECT_TIMEOUT = 20000; /** * Test ClassLoader */ public final static String FORCE_DELEGATE_TO_TEST_CLASSLOADER = "com.alipay.sofa.ark.delegate.to.testClassLoader"; public final static String FORCE_DELEGATE_TO_APP_CLASSLOADER = "com.alipay.sofa.ark.delegate.to.appClassLoader"; public final static String EXTENSION_EXCLUDES = "excludes"; public final static String EXTENSION_EXCLUDES_GROUPIDS = "excludeGroupIds"; public final static String EXTENSION_EXCLUDES_ARTIFACTIDS = "excludeArtifactIds"; public final static String EXTENSION_INCLUDES = "includes"; public final static String EXTENSION_INCLUDES_GROUPIDS = "includeGroupIds"; public final static String EXTENSION_INCLUDES_ARTIFACTIDS = "includeArtifactIds"; public final static String DECLARED_LIBRARIES_WHITELIST = "declared.libraries.whitelist"; public static final List CHANNEL_QUIT = new ArrayList<>(); static { CHANNEL_QUIT.add("quit"); CHANNEL_QUIT.add("q"); CHANNEL_QUIT.add("exit"); } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/AbstractArkEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/1 10:45 AM * @since **/ public abstract class AbstractArkEvent implements ArkEvent { protected transient T source; protected String topic; protected long timestamp; public AbstractArkEvent(T source) { this.source = source; this.timestamp = System.currentTimeMillis(); } public T getSource() { return source; } public void setSource(T source) { this.source = source; } @Override public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public long getTimestamp() { return timestamp; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/AfterFinishDeployEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event; import com.alipay.sofa.ark.spi.constant.Constants; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/4 3:31 PM * @since **/ public class AfterFinishDeployEvent extends AbstractArkEvent { public AfterFinishDeployEvent() { super(Constants.EMPTY_STR); this.topic = Constants.ARK_EVENT_TOPIC_AFTER_FINISH_DEPLOY_STAGE; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/AfterFinishStartupEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event; import com.alipay.sofa.ark.spi.constant.Constants; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/4 3:31 PM * @since **/ public class AfterFinishStartupEvent extends AbstractArkEvent { public AfterFinishStartupEvent() { super(Constants.EMPTY_STR); this.topic = Constants.ARK_EVENT_TOPIC_AFTER_FINISH_STARTUP_STAGE; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/ArkEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event; /** * An Event * * @author qilong.zql * @since 0.4.0 */ public interface ArkEvent { /** * Returns the topic of event * * @return String return event topic */ String getTopic(); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/biz/AfterAllBizStartupEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event.biz; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.model.Biz; /** * @author lvjing2 (leojames.googol@gmail.com) 2024/12/1 6:17 PM * @since v2.2.16 * this only used when the base restart to recovery all the bizs which had been installed before the restart **/ public class AfterAllBizStartupEvent extends AbstractArkEvent { public AfterAllBizStartupEvent() { super(null); this.topic = Constants.BIZ_EVENT_TOPIC_AFTER_INVOKE_ALL_BIZ_START; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/biz/AfterBizStartupEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event.biz; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.model.Biz; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/1 6:17 PM * @since **/ public class AfterBizStartupEvent extends AbstractArkEvent { public AfterBizStartupEvent(Biz source) { super(source); this.topic = Constants.BIZ_EVENT_TOPIC_AFTER_INVOKE_BIZ_START; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/biz/AfterBizStartupFailedEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event.biz; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.model.Biz; /** * @author lianglipeng.llp@alibaba-inc.com * @since 2.2.9 */ public class AfterBizStartupFailedEvent extends AbstractArkEvent { private Throwable throwable; public AfterBizStartupFailedEvent(Biz source, Throwable e) { super(source); this.throwable = e; this.topic = Constants.BIZ_EVENT_TOPIC_AFTER_BIZ_FAILED; } public Throwable getThrowable() { return throwable; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/biz/AfterBizStopEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event.biz; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.model.Biz; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/1 6:21 PM * @since **/ public class AfterBizStopEvent extends AbstractArkEvent { public AfterBizStopEvent(Biz source) { super(source); this.topic = Constants.BIZ_EVENT_TOPIC_AFTER_INVOKE_BIZ_STOP; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/biz/AfterBizStopFailedEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event.biz; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.model.Biz; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/1 6:21 PM * @since **/ public class AfterBizStopFailedEvent extends AbstractArkEvent { private Throwable throwable; public AfterBizStopFailedEvent(Biz source, Throwable t) { super(source); this.throwable = t; this.topic = Constants.BIZ_EVENT_TOPIC_AFTER_INVOKE_BIZ_STOP_FAILED; } public Throwable getThrowable() { return throwable; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/biz/AfterBizSwitchEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event.biz; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.model.Biz; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/4 4:29 PM * @since **/ public class AfterBizSwitchEvent extends AbstractArkEvent { public AfterBizSwitchEvent(Biz source) { super(source); this.topic = Constants.BIZ_EVENT_TOPIC_AFTER_INVOKE_BIZ_SWITCH; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/biz/BeforeBizRecycleEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event.biz; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.model.Biz; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/1 6:21 PM * @since **/ public class BeforeBizRecycleEvent extends AbstractArkEvent { public BeforeBizRecycleEvent(Biz source) { super(source); this.topic = Constants.BIZ_EVENT_TOPIC_BEFORE_RECYCLE_BIZ; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/biz/BeforeBizStartupEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event.biz; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.model.Biz; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/1 10:56 AM * @since **/ public class BeforeBizStartupEvent extends AbstractArkEvent { public BeforeBizStartupEvent(Biz source) { super(source); this.topic = Constants.BIZ_EVENT_TOPIC_BEFORE_INVOKE_BIZ_START; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/biz/BeforeBizStopEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event.biz; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.model.Biz; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/1 6:20 PM * @since **/ public class BeforeBizStopEvent extends AbstractArkEvent { public BeforeBizStopEvent(Biz source) { super(source); this.topic = Constants.BIZ_EVENT_TOPIC_BEFORE_INVOKE_BIZ_STOP; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/biz/BeforeBizSwitchEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event.biz; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.model.Biz; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/4 4:28 PM * @since **/ public class BeforeBizSwitchEvent extends AbstractArkEvent { public BeforeBizSwitchEvent(Biz source) { super(source); this.topic = Constants.BIZ_EVENT_TOPIC_BEFORE_INVOKE_BIZ_SWITCH; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/plugin/AfterPluginStartupEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event.plugin; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.model.Plugin; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/1 6:17 PM * @since **/ public class AfterPluginStartupEvent extends AbstractArkEvent { public AfterPluginStartupEvent(Plugin source) { super(source); this.topic = Constants.PLUGIN_EVENT_TOPIC_AFTER_INVOKE_PLUGIN_START; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/plugin/AfterPluginStopEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event.plugin; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.model.Plugin; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/1 6:21 PM * @since **/ public class AfterPluginStopEvent extends AbstractArkEvent { public AfterPluginStopEvent(Plugin source) { super(source); this.topic = Constants.PLUGIN_EVENT_TOPIC_AFTER_INVOKE_PLUGIN_STOP; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/plugin/BeforePluginStartupEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event.plugin; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.model.Plugin; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/1 10:56 AM * @since **/ public class BeforePluginStartupEvent extends AbstractArkEvent { public BeforePluginStartupEvent(Plugin source) { super(source); this.topic = Constants.PLUGIN_EVENT_TOPIC_BEFORE_INVOKE_PLUGIN_START; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/event/plugin/BeforePluginStopEvent.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.event.plugin; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.model.Plugin; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/1 6:20 PM * @since **/ public class BeforePluginStopEvent extends AbstractArkEvent { public BeforePluginStopEvent(Plugin source) { super(source); this.topic = Constants.PLUGIN_EVENT_TOPIC_BEFORE_INVOKE_PLUGIN_STOP; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/ext/ExtResponse.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.ext; public class ExtResponse { private boolean success; private String errorMsg; private String errorCode; private T data; public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getErrorMsg() { return errorMsg; } public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } public String getErrorCode() { return errorCode; } public void setErrorCode(String errorCode) { this.errorCode = errorCode; } public T getData() { return data; } public void setData(T data) { this.data = data; } @Override public String toString() { return "ExtResponse{" + "success=" + success + ", errorMsg='" + errorMsg + '\'' + ", errorCode='" + errorCode + '\'' + ", data=" + data + '}'; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/ext/ExtServiceProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.ext; public interface ExtServiceProvider { ExtResponse invokeService(String action, String param); String getType(); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/model/Biz.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.model; import com.alipay.sofa.ark.spi.service.PriorityOrdered; import java.net.URL; import java.util.Map; /** * Ark Biz Model Interface * * @author ruoshan * @since 0.1.0 */ public interface Biz extends BizInfo, PriorityOrdered { /** * start Biz * @param args * @throws Throwable */ void start(String[] args) throws Throwable; /** * start Biz with args and envs * @param args * @param envs * @throws Throwable */ void start(String[] args, Map envs) throws Throwable; /** * stop Biz * @throws Throwable */ void stop() throws Throwable; /** * check resource whether declared in this biz classLoader. * @param url */ boolean isDeclared(URL url, String resourceName); /** * check whether this biz is declared mode. * declared mode means this biz can only delegate declared class and resources * in the pom of this biz to other classloader like plugin or master Biz. * @return */ boolean isDeclaredMode(); /** * allow to dynamic update biz name * @param bizName */ void setCustomBizName(String bizName); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/model/BizConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.model; import java.net.URL; import java.util.List; import java.util.Map; public class BizConfig { /** * Biz部署参数指定version */ private String specifiedVersion; /** * Biz扩展classpath */ private URL[] extensionUrls; /** * Biz依赖的plugins */ private List dependentPlugins; String[] args; Map envs; public String getSpecifiedVersion() { return specifiedVersion; } public void setSpecifiedVersion(String specifiedVersion) { this.specifiedVersion = specifiedVersion; } public URL[] getExtensionUrls() { return extensionUrls; } public void setExtensionUrls(URL[] extensionUrls) { this.extensionUrls = extensionUrls; } public List getDependentPlugins() { return dependentPlugins; } public void setDependentPlugins(List dependentPlugins) { this.dependentPlugins = dependentPlugins; } public String[] getArgs() { return args; } public void setArgs(String[] args) { this.args = args; } public Map getEnvs() { return envs; } public void setEnvs(Map envs) { this.envs = envs; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/model/BizInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.model; import java.net.URL; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; /** * @author qilong.zql * @since 0.6.0 */ public interface BizInfo { /** * get Biz Name * @return biz name */ String getBizName(); /** * get Biz Version */ String getBizVersion(); /** * get identity id in runtime, an unique-id of ark biz * @return */ String getIdentity(); /** * get Biz Main Entry Class Name * @return main class name */ String getMainClass(); /** * get Biz Class Path * @return biz classpath */ URL[] getClassPath(); /** * get biz url */ URL getBizUrl(); /** * get denied imported packages config * @return */ Set getDenyImportPackages(); /** * get biz deny import package which is exactly matched * @return */ Set getDenyImportPackageNodes(); /** * get biz deny import package which is matched by prefix * @return */ Set getDenyImportPackageStems(); /** * get denied imported classes * @return */ Set getDenyImportClasses(); /** * get denied imported resources * @return */ Set getDenyImportResources(); /** * get denied imported resource stems by prefix * @return denied imported resource stems */ Set getDenyPrefixImportResourceStems(); /** * get denied imported resource stems by suffix * @return denied imported resource stems */ Set getDenySuffixImportResourceStems(); /** * get Biz Classloader * @return biz classloader */ ClassLoader getBizClassLoader(); /** * get Biz State */ BizState getBizState(); /** * get web context path */ String getWebContextPath(); /** * get Biz attributes * @return */ Map getAttributes(); /** * get getBizStateChangeLog * @since 2.2.9 * @return java.util.concurrent.CopyOnWriteArrayList */ List getBizStateRecords(); class BizStateRecord { private final Date changeTime; private final BizState state; private final StateChangeReason reason; private final String message; private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); static { sdf.setTimeZone(TimeZone.getDefault()); } public BizStateRecord(Date changeTime, BizState state) { this.changeTime = changeTime; this.state = state; this.reason = StateChangeReason.UNDEFINE; this.message = ""; } public BizStateRecord(Date changeTime, BizState state, StateChangeReason reason, String message) { this.changeTime = changeTime; this.state = state; this.reason = reason; this.message = message; } @Override public String toString() { String date = sdf.format(changeTime); return String.format("%s -> %s with reason: %s and message: %s", date, state, reason, message); } public Date getChangeTime() { return changeTime; } public BizState getState() { return state; } public StateChangeReason getReason() { return reason; } public String getMessage() { return message; } } enum StateChangeReason { /** * 模块被创建 */ CREATED("Created"), /** * 模块启动成功 */ STARTED("Started"), /** * 模块启动失败 */ INSTALL_FAILED("Install Failed"), /** * 模块卸载失败 */ UN_INSTALL_FAILED("Uninstall Failed"), /** * 模块被切换为 ACTIVATED 或 DEACTIVATED 状态 */ SWITCHED("Switched"), /** * 模块正在停止 */ KILLING("Killing"), /** * 模块已停止 */ STOPPED("Stopped"), /** * 默认值:未定义 */ UNDEFINE("Undefine"); private final String reason; StateChangeReason(String reason) { this.reason = reason; } public String getReason() { return reason; } @Override public String toString() { return getReason(); } } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/model/BizOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.model; import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * @author qilong.zql * @since 0.6.0 */ public class BizOperation { private String bizName; private String bizVersion; private OperationType operationType; private Map parameters = new HashMap<>(); public boolean isValid() { return operationType == OperationType.UNKNOWN; } public String getBizName() { return bizName; } public BizOperation setBizName(String bizName) { this.bizName = bizName; return this; } public String getBizVersion() { return bizVersion; } public BizOperation setBizVersion(String bizVersion) { this.bizVersion = bizVersion; return this; } public OperationType getOperationType() { return operationType; } public BizOperation setOperationType(OperationType operationType) { this.operationType = operationType; return this; } public Map getParameters() { return parameters; } public BizOperation setParameters(Map parameters) { this.parameters = parameters; return this; } public BizOperation putParameter(String key, String value) { this.parameters.put(key, value); return this; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj == null || !(obj instanceof BizOperation)) { return false; } BizOperation that = (BizOperation) obj; if (!Objects.equals(this.getBizName(), that.getBizName())) { return false; } if (!Objects.equals(this.getBizVersion(), that.getBizVersion())) { return false; } if (!Objects.equals(this.getOperationType(), that.getOperationType())) { return false; } return true; } public static BizOperation createBizOperation() { return new BizOperation(); } public enum OperationType { INSTALL, UNINSTALL, SWITCH, CHECK, UNKNOWN } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/model/BizState.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.model; /** * Biz State * * @author qilong.zql * @since 0.4.0 */ public enum BizState { /** * not init or not start install yet */ UNRESOLVED("unresolved"), /** * installing */ RESOLVED("resolved"), /** * install succeed, and start serving */ ACTIVATED("activated"), /** * install succeed, but stop serving, usually caused by a new version installed */ DEACTIVATED("deactivated"), /** * install or uninstall failed. */ BROKEN("broken"), /** * uninstall succeed */ STOPPED("stopped"); private String state; BizState(String state) { this.state = state; } public String getBizState() { return state; } @Override public String toString() { return getBizState(); } public static BizState of(String state) { if (UNRESOLVED.name().equalsIgnoreCase(state)) { return UNRESOLVED; } else if (RESOLVED.name().equalsIgnoreCase(state)) { return RESOLVED; } else if (ACTIVATED.name().equalsIgnoreCase(state)) { return ACTIVATED; } else if (DEACTIVATED.name().equalsIgnoreCase(state)) { return DEACTIVATED; } else if (STOPPED.name().equalsIgnoreCase(state)) { return STOPPED; } else { return BROKEN; } } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/model/Plugin.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.model; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.service.PriorityOrdered; import java.net.URL; import java.util.Set; /** * Ark Plugin Module Interface * * @author qilong.zql * @since 0.1.0 */ public interface Plugin extends PriorityOrdered { /** * get Plugin Name * @return plugin name */ String getPluginName(); /** * get Plugin GroupId * @return plugin groupId */ String getGroupId(); /** * get Plugin ArtifactId * @return plugin artifactId */ String getArtifactId(); /** * get Plugin Version * @return version */ String getVersion(); /** * get Plugin Priority, mainly used in following 3 scenarios: * 1. Plugin start up order * 2. Plugin export class load priority * 3. Plugin Service * priority is higher as the number is smaller * @return plugin priority */ int getPriority(); /** * get Plugin Activator * @return plugin activator */ String getPluginActivator(); /** * get Plugin Class Path * @return plugin class path */ URL[] getClassPath(); /** * get Plugin ClassLoader * @return plugin classLoader */ ClassLoader getPluginClassLoader(); /** * get Plugin Runtime Context * @return plugin context */ PluginContext getPluginContext(); /** * get Plugin Export Mode * default exportMode = classLoader, means export class to load in this plugin classLoader * exportMode = override, means export class to file only, and usually will be reload in another classLoader * @return */ String getExportMode(); /** * get Plugin Export Packages Config * @return plugin export packages */ Set getExportPackages(); /** * get plugin export package which is exactly matched * @return */ Set getExportPackageNodes(); /** * get plugin export package which is matched by prefix * @return */ Set getExportPackageStems(); /** * get plugin Export Classes * @return plugin export classes */ Set getExportClasses(); /** * get Plugin Import Packages Config * @return plugin import packages */ Set getImportPackages(); /** * get plugin import package which is exactly matched * @return */ Set getImportPackageNodes(); /** * get plugin import package which is matched by prefix * @return */ Set getImportPackageStems(); /** * get Plugin Import Classes * @return plugin import classes */ Set getImportClasses(); /** * get Plugin Import Resources * @return plugin import resources */ Set getImportResources(); /** * get Plugin Import Resources matched by prefix * @return plugin Import Resources matched by prefix */ Set getImportPrefixResourceStems(); /** * get Plugin Import Resources matched by suffix * @return plugin Import Resources matched by suffix */ Set getImportSuffixResourceStems(); /** * get Plugin Export Resources * @return get plugin export resources */ Set getExportResources(); /** * get plugin export resources matched by prefix * @return plugin export resources matched by prefix */ Set getExportPrefixResourceStems(); /** * get plugin export resources matched by suffix * @return get plugin export resources matched by suffix */ Set getExportSuffixResourceStems(); /** * get Plugin Archive URL * @return plugin archive url */ URL getPluginURL(); /** * start Plugin * @throws ArkRuntimeException */ void start() throws ArkRuntimeException; /** * stop Plugin * @throws ArkRuntimeException */ void stop() throws ArkRuntimeException; } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/model/PluginConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.model; import java.net.URL; public class PluginConfig { /** * Plugin部署参数指定name */ private String specifiedName; /** * Plugin部署参数指定version */ private String specifiedVersion; /** * Plugin扩展classpath */ private URL[] extensionUrls; public String getSpecifiedName() { return specifiedName; } public void setSpecifiedName(String specifiedName) { this.specifiedName = specifiedName; } public String getSpecifiedVersion() { return specifiedVersion; } public void setSpecifiedVersion(String specifiedVersion) { this.specifiedVersion = specifiedVersion; } public URL[] getExtensionUrls() { return extensionUrls; } public void setExtensionUrls(URL[] extensionUrls) { this.extensionUrls = extensionUrls; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/model/PluginContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.model; import com.alipay.sofa.ark.spi.registry.ServiceFilter; import com.alipay.sofa.ark.spi.registry.ServiceReference; import java.util.List; import java.util.Set; /** * SOFAArk Plugin Runtime Context * * @author qilong.zql * @since 0.1.0 */ public interface PluginContext { /** * get Plugin * @return plugin */ Plugin getPlugin(); /** * get Plugin by Plugin Name * @param pluginName plugin name * @return plugin */ Plugin getPlugin(String pluginName); /** * get All Plugin Names * @return */ Set getPluginNames(); /** * get Plugin ClassLoader * @return plugin classloader */ ClassLoader getClassLoader(); /** * Publish Plugin Service * @param ifClass service interface * @param implObject service implement object * @param * @return */ ServiceReference publishService(Class ifClass, T implObject); /** * Publish Plugin Service * @param ifClass service interface * @param implObject service implement object * @param uniqueId service implementation id * @param * @return */ ServiceReference publishService(Class ifClass, T implObject, String uniqueId); /** * Get Service publish by plugin, when there are multiple services, return the highest priority plugin service * @param ifClass service interface * @param * @return service reference */ ServiceReference referenceService(Class ifClass); /** * Get Service publish by one specific plugin * @param ifClass service interface * @param * @param uniqueId service implementation * @return service reference */ ServiceReference referenceService(Class ifClass, String uniqueId); /** * Get Service List publish by plugin * @param serviceFilter service filter * @return */ List referenceServices(ServiceFilter serviceFilter); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/model/PluginOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.model; import java.io.File; import java.util.List; public class PluginOperation { private String pluginName; private String pluginVersion; private OperationType operationType; private String url; private File localFile; private List extensionLibs; public String getPluginName() { return pluginName; } public void setPluginName(String pluginName) { this.pluginName = pluginName; } public String getPluginVersion() { return pluginVersion; } public void setPluginVersion(String pluginVersion) { this.pluginVersion = pluginVersion; } public OperationType getOperationType() { return operationType; } public void setOperationType(OperationType operationType) { this.operationType = operationType; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public File getLocalFile() { return localFile; } public void setLocalFile(File localFile) { this.localFile = localFile; } public List getExtensionLibs() { return extensionLibs; } public void setExtensionLibs(List extensionLibs) { this.extensionLibs = extensionLibs; } public enum OperationType { INSTALL, UNINSTALL } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/pipeline/Pipeline.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.pipeline; /** * Pipeline Interface * * @author ruoshan * @since 0.1.0 */ public interface Pipeline extends PipelineStage { /** * Add pipeline stage in pipeline * @param pipelineStage pipeline stage * @return pipeline */ Pipeline addPipelineStage(PipelineStage pipelineStage); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/pipeline/PipelineContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.pipeline; import com.alipay.sofa.ark.spi.archive.ExecutableArchive; import com.alipay.sofa.ark.spi.argument.LaunchCommand; /** * Pipeline Context * * @author ruoshan * @since 0.1.0 */ public class PipelineContext { private ExecutableArchive executableArchive; private LaunchCommand launchCommand; public ExecutableArchive getExecutableArchive() { return executableArchive; } public void setExecutableArchive(ExecutableArchive executableArchive) { this.executableArchive = executableArchive; } public LaunchCommand getLaunchCommand() { return launchCommand; } public void setLaunchCommand(LaunchCommand launchCommand) { this.launchCommand = launchCommand; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/pipeline/PipelineStage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.pipeline; import com.alipay.sofa.ark.exception.ArkRuntimeException; /** * Pipeline stage interface * * @author ruoshan * @since 0.1.0 */ public interface PipelineStage { /** * Process current pipeline stage * @param pipelineContext pipeline context * @throws ArkRuntimeException */ void process(PipelineContext pipelineContext) throws ArkRuntimeException; } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/registry/ServiceFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.registry; /** * Service Filter Interface * * @author ruoshan * @since 0.1.0 */ public interface ServiceFilter { /** * Filter ServiceReference * * @param serviceReference whether the given serviceReference * match {@code ServiceFilter} * @return true if match; false if not match */ boolean match(ServiceReference serviceReference); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/registry/ServiceMetadata.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.registry; /** * Service Metadata which contains Service metadata information * Service is unique by service anme and plugin name * * @author qilong.zql * @since 0.1.0 */ public interface ServiceMetadata { /** * get service id, different service implementation of same interface can be * recognised by uniqueId * * @return service name */ String getUniqueId(); /** * get Service Interface Class * @return interface class */ Class getInterfaceClass(); /** * get ServiceProvider * @return */ ServiceProvider getServiceProvider(); /** * Service name, generally speaking, it's combined by {@link ServiceMetadata#getUniqueId()} and * {@link ServiceMetadata#getInterfaceClass} * * @return */ String getServiceName(); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/registry/ServiceProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.registry; import com.alipay.sofa.ark.spi.service.PriorityOrdered; /** * Service Provider Interface * @author ruoshan * @since 0.1.0 */ public interface ServiceProvider extends PriorityOrdered { /** * Get Service Provider Type, see {@link ServiceProviderType} * @return service provider type */ ServiceProviderType getServiceProviderType(); /** * Get Service Provider Description * @return service provider Description */ String getServiceProviderDesc(); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/registry/ServiceProviderType.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.registry; /** * Define Service Provider Type * * @author ruoshan * @since 0.1.0 */ public enum ServiceProviderType { ARK_PLUGIN("Ark plugin"), ARK_CONTAINER("Ark Container"), ARK_MASTER_BIZ("Ark Master Biz"); private String desc; ServiceProviderType(String desc) { this.desc = desc; } public String getDesc() { return desc; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/registry/ServiceReference.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.registry; import com.alipay.sofa.ark.spi.service.PriorityOrdered; /** * Service Reference which maintain Service and Service Metadata * * @author qilong.zql * @since 0.1.0 */ public interface ServiceReference extends PriorityOrdered { /** * get Service Object * @return service */ T getService(); /** * get Service Metadata * @return */ ServiceMetadata getServiceMetadata(); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/replay/Replay.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.replay; /** * @author qilong.zql 17/11/10-11:52 * @author guolei.sgl 19/7/21 16:48 */ public interface Replay { Object invoke(); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/replay/ReplayContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.replay; import java.util.Stack; /** * @author qilong.zql 17/11/10-12:15 * @author guolei.sgl 19/7/21 16:48 */ public class ReplayContext { private static final ThreadLocal> moduleVersions = new ThreadLocal>() { @Override protected Stack initialValue() { return new Stack<>(); } }; public static final String PLACEHOLDER = "__call_placeholder__"; public static void set(String version) { moduleVersions.get().push(version); } public static void unset() { moduleVersions.get().pop(); } public static String get() { if (moduleVersions.get().size() == 0) { return null; } return moduleVersions.get().peek(); } public static void setPlaceHolder() { // PLACEHOLDER needs to be placed when the link is currently invoked at the version number if (moduleVersions.get().size() != 0) { moduleVersions.get().push(PLACEHOLDER); } } public static void clearPlaceHolder() { if (PLACEHOLDER.equals(get())) { moduleVersions.get().pop(); } } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/ArkInject.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service; import java.lang.annotation.ElementType; /** * Any plugin can publish a service, in this service, * * @author qilong.zql * @since 0.4.0 */ @java.lang.annotation.Target(ElementType.FIELD) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Documented public @interface ArkInject { /** * ark service interface * @return */ Class interfaceType() default void.class; /** * ark service uniqueId * * @return return reference unique-id */ String uniqueId() default ""; } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/ArkService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service; import com.alipay.sofa.ark.exception.ArkRuntimeException; /** * Ark Service lifecycle, when a service need init/dispose action, it should implement this interface and register by guice * * @author ruoshan * @since 0.1.0 */ public interface ArkService extends PriorityOrdered { /** * Ark Service init * @throws ArkRuntimeException */ void init() throws ArkRuntimeException; /** * Ark Service dispose * @throws ArkRuntimeException */ void dispose() throws ArkRuntimeException; } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/PluginActivator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.model.PluginContext; /** * Plugin Activator which defines Plugin Entry * * @author qilong.zql * @since 0.1.0 */ public interface PluginActivator { /** * Start Plugin * @param context plugin context * @throws ArkRuntimeException */ void start(PluginContext context); /** * Stop Plugin * @param context * @throws ArkRuntimeException */ void stop(PluginContext context); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/PriorityOrdered.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service; /** * {@code PriorityOrdered} is an interface that can be implemented by objects that * should be ordered. * * @author qilong.zql * @since 0.4.0 */ public interface PriorityOrdered { /** * Useful constant for the highest precedence value. * @see java.lang.Integer#MIN_VALUE */ int HIGHEST_PRECEDENCE = Integer.MIN_VALUE; /** * Useful constant for the lowest precedence value. * @see java.lang.Integer#MAX_VALUE */ int LOWEST_PRECEDENCE = Integer.MAX_VALUE; /** * Default priority */ int DEFAULT_PRECEDENCE = 100; /** * Get the order value of this object. Higher values are interpreted as lower * priority. As a consequence, the object with the lowest value has the highest * priority. * @return */ int getPriority(); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/biz/AddBizToStaticDeployHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.biz; import com.alipay.sofa.ark.spi.archive.BizArchive; import com.alipay.sofa.ark.spi.service.extension.Extensible; import java.util.List; /** * @author lianglipeng.llp@alibaba-inc.com * @version $Id: BeforeStaticDeployBizHook.java, v 0.1 2024年06月27日 01:57 立蓬 Exp $ */ @Extensible public interface AddBizToStaticDeployHook { List getStaticBizToAdd() throws Exception; } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/biz/BizDeployService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.biz; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.service.ArkService; /** * Service to deploy Biz * * @author qilong.zql * @since 0.4.0 */ public interface BizDeployService extends ArkService { /** * Deploy all ark biz * @param args biz startup arguments * @throws ArkRuntimeException throw exception when meets error */ void deploy(String[] args) throws ArkRuntimeException; /** * Un-deploy all ark ark biz * @throws ArkRuntimeException */ void unDeploy() throws ArkRuntimeException; } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/biz/BizDeployer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.biz; /** * BizDeployer to deploy Biz * * @author qilong.zql * @since 0.4.0 */ public interface BizDeployer { /** * Initialize biz deployer * * @param args command line arguments */ void init(String[] args); /** * Start to deploy biz */ void deploy(); /** * un-deploy biz, whose resources and service would be unloaded. */ void unDeploy(); /** * Get description of biz deployer * * @return description */ String getDesc(); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/biz/BizFactoryService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.biz; import com.alipay.sofa.ark.spi.archive.BizArchive; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizConfig; import com.alipay.sofa.ark.spi.model.BizOperation; import java.io.File; import java.io.IOException; import java.net.URL; /** * Create Biz according to {@link File} and {@link BizArchive} * * @author qilong.zql * @since 0.4.0 */ public interface BizFactoryService { /** * @param bizArchive * @return * @throws IOException */ Biz createBiz(BizArchive bizArchive) throws IOException; /** * Create Biz Model according to {@link BizArchive} * * @param bizArchive the {@link BizArchive} model * @return Biz * @throws IOException throw io exception when {@link BizArchive} is invalid. */ Biz createBiz(BizArchive bizArchive, URL[] extensionUrls) throws IOException; /** * Create Biz Model according to {@link File} * * @param file the ark biz file * @return Biz * @throws IOException throw io exception when {@link File} is invalid. */ Biz createBiz(File file) throws IOException; /** * @param file * @param extensionUrls * @return * @throws IOException */ Biz createBiz(File file, URL[] extensionUrls) throws IOException; /** * @param bizOperation * @param file * @return */ Biz createBiz(BizOperation bizOperation, File file) throws IOException; /** * @param file * @param bizConfig * @return * @throws IOException */ Biz createBiz(File file, BizConfig bizConfig) throws IOException; /** * @param bizArchive * @param bizConfig * @return * @throws IOException */ Biz createBiz(BizArchive bizArchive, BizConfig bizConfig) throws IOException; /** * Create Biz Model according to master biz * @return */ Biz createEmbedMasterBiz(ClassLoader masterClassLoader); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/biz/BizManagerService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.biz; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizState; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; /** * Service to manage biz * * @author ruoshan * @since 0.1.0 */ public interface BizManagerService { /** * Register Biz * * @param biz * @return */ boolean registerBiz(Biz biz); /** * Un-Register Biz, it requires the biz state must be {@link BizState#ACTIVATED} * or {@link BizState#DEACTIVATED} or {@link BizState#BROKEN} * @param bizName Biz Name * @param bizVersion Biz Version * @return Biz */ Biz unRegisterBiz(String bizName, String bizVersion); /** * Un-Register Biz in strict mode, it ignores the biz state, generally invoked when install biz failed. * @param bizName Biz Name * @param bizVersion Biz Version * @return Biz */ Biz unRegisterBizStrictly(String bizName, String bizVersion); /** * Get Biz List by name * * @param bizName * @return */ List getBiz(String bizName); /** * Get Biz determined by bizName and BizVersion * * @param bizName Biz Name * @param bizVersion Biz Version * @return */ Biz getBiz(String bizName, String bizVersion); /** * Get Biz by identity id, an identity is usually consist of * biz name and biz version. * * @param bizIdentity * @return */ Biz getBizByIdentity(String bizIdentity); /** * Get Biz by biz ClassLoader * @param classLoader * @return */ Biz getBizByClassLoader(ClassLoader classLoader); /** * get All biz names * * @return */ Set getAllBizNames(); Set getAllBizIdentities(); /** * Get all biz in priority PriorityOrdered * @return */ List getBizInOrder(); /** * Get active biz with given biz name whose state * is {@link BizState#ACTIVATED} * * @param bizName * @return */ Biz getActiveBiz(String bizName); /** * Check whether the biz specified with a given name and a given version * is active {@link BizState#ACTIVATED} * * @param bizName * @param bizVersion * @return */ boolean isActiveBiz(String bizName, String bizVersion); /** * Active biz with specified biz name and biz version. * @param bizName * @param bizVersion */ void activeBiz(String bizName, String bizVersion); /** * Get {@link BizState} according to biz name and biz version. * * @param bizName * @param bizVersion * @return */ BizState getBizState(String bizName, String bizVersion); /** * Get {@link BizState} according to biz identity. * * @param bizIdentity * @return */ BizState getBizState(String bizIdentity); /** * dynamic to instead a biz * @param addingBiz * @param removing * @return */ boolean removeAndAddBiz(Biz addingBiz, Biz removing); ConcurrentHashMap> getBizRegistration(); /** * get the lock for the biz with the given name * @param bizName * @return */ ReentrantLock getBizLock(String bizName); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/classloader/ClassLoaderHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.classloader; import com.alipay.sofa.ark.spi.service.extension.Extensible; import java.io.IOException; import java.net.URL; import java.util.Enumeration; /** * implementation of ClassLoaderHook is used to customize the strategy of loading class and resources. * * @author qilong.zql * @since 0.6.0 * @param {@link com.alipay.sofa.ark.spi.model.Plugin} or {@link com.alipay.sofa.ark.spi.model.Biz} */ @Extensible public interface ClassLoaderHook { /** * invoke this method before load class using biz classLoader or plugin classLoader. If this method * returns null then normal loading process is done. If this method returns a non-null value then the * rest of the loading process is skipped and the returned value is used. * * @param name class name * @param classLoaderService {@link ClassLoaderService} * @param t plugin or biz instance where this hook is invoked * @return the class found by this hook or null if normal loading process should continue * @throws ClassNotFoundException to terminate the hook and throw an exception */ Class preFindClass(String name, ClassLoaderService classLoaderService, T t) throws ClassNotFoundException; /** * This method will only be called if no class was found from the normal loading process. * * @param name class name * @param classLoaderService {@link ClassLoaderService} * @param t plugin or biz instance where this hook is invoked * @return the class found by this hook or null if normal loading process should continue * @throws ClassNotFoundException to terminate the hook and throw an exception */ Class postFindClass(String name, ClassLoaderService classLoaderService, T t) throws ClassNotFoundException; /** * invoke this method before load resource using biz classLoader or plugin classLoader. If this method * returns null then normal loading process is done. If this method returns a non-null value then the * rest of the loading process is skipped and the returned value is used. * * @param name resource name * @param classLoaderService {@link ClassLoaderService} * @param t plugin or biz instance where this hook is invoked * @return the resource found by this hook or null if normal loading process should continue */ URL preFindResource(String name, ClassLoaderService classLoaderService, T t); /** * This method will only be called if no resource was found from the normal loading process. * * @param name resource name * @param classLoaderService {@link ClassLoaderService} * @param t plugin or biz instance where this hook is invoked * @return the resource found by this hook or null if normal loading process should continue */ URL postFindResource(String name, ClassLoaderService classLoaderService, T t); /** * If this method returns null then normal loading process is done. * If this method returns a non-null value then the rest of the loading process is skipped and the returned * value is used. * If this method throws an FileNotFoundException then the loading process is terminated * @param name the name of the resource to find * @param classLoaderService {@link ClassLoaderService} * @param t plugin or biz instance where this hook is invoked * @return the resources found by this hook or empty if normal loading process should continue * @throws IOException throw an exception when error occurs. */ Enumeration preFindResources(String name, ClassLoaderService classLoaderService, T t) throws IOException; /** * This method will only be called if no resources were found from the normal loading process. * * @param name the name of the resource to find * @param classLoaderService {@link ClassLoaderService} * @param t plugin or biz instance where this hook is invoked * @return the resources found by this hook or empty if normal loading process should continue * @throws IOException throw an exception when error occurs. */ Enumeration postFindResources(String name, ClassLoaderService classLoaderService, T t) throws IOException; } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/classloader/ClassLoaderService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.classloader; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.ArkService; import java.util.List; /** * ClassLoader Service * * @author ruoshan * @since 0.1.0 */ public interface ClassLoaderService extends ArkService { /** * prepare plugin exported class and resource index Cache */ void prepareExportClassAndResourceCache(); /** * Whether class is sun reflect related class * @param className class name * @return */ boolean isSunReflectClass(String className); /** * Whether class is ark spi class * @param className class name * @return */ boolean isArkSpiClass(String className); /** * Whether class is ark api class * @param className class name * @return */ boolean isArkApiClass(String className); /** * Whether class is ark log class. * @param className * @return */ boolean isArkLogClass(String className); /** * Whether class is ark exception class. * @param className * @return */ boolean isArkExceptionClass(String className); /** * Whether class is in import-class * @param pluginName plugin name * @param className class name * @return */ boolean isClassInImport(String pluginName, String className); /** * Find export mode for class className * @param className * @return */ String getExportMode(String className); /** * Find classloader which export class for import class * @param className class name * @return */ ClassLoader findExportClassLoader(String className); ClassLoader findExportClassLoaderByBiz(Biz biz, String className); Plugin findExportPlugin(String className); /** * Whether resource is in import-resources * @param pluginName * @param resourceName * @return */ boolean isResourceInImport(String pluginName, String resourceName); /** * Find classloaders which export resource for import resource in priority orders for import-resources * @param resourceName resource name * @return classloader list */ List findExportResourceClassLoadersInOrder(String resourceName); List findExportResourceClassLoadersInOrderByBiz(Biz biz, String resourceName); /** * Get JDK Related class classloader * @return */ ClassLoader getJDKClassLoader(); /** * Get Ark Container classloader * @return */ ClassLoader getArkClassLoader(); /** * Get system classloader * @return */ ClassLoader getSystemClassLoader(); /** * Get java agent classloader * @return */ ClassLoader getAgentClassLoader(); /** * Get Ark Biz ClassLoader * @return */ ClassLoader getBizClassLoader(String bizIdentity); /** * Get Ark Master Biz ClassLoader * @return */ ClassLoader getMasterBizClassLoader(); /** * Get Ark Plugin ClassLoader * @param pluginName * @return */ ClassLoader getPluginClassLoader(String pluginName); /** * Whether class is denied by biz * @param bizIdentity biz identity * @param className class name * @return */ boolean isDeniedImportClass(String bizIdentity, String className); /** * Whether resource is denied by biz * @param bizIdentity biz identity * @param resourceName resource name * @return */ boolean isDeniedImportResource(String bizIdentity, String resourceName); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/event/EventAdminService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.event; import com.alipay.sofa.ark.spi.event.ArkEvent; /** * @author qilong.zql * @since 0.4.0 */ public interface EventAdminService { /** * Initiate synchronous delivery of an event. This method does not return to * the caller until delivery of the event is completed. * * @param event The event to send to all listeners which subscribe to the * topic of the event. */ void sendEvent(ArkEvent event); /** * Register an event handler * * @param eventHandler */ void register(EventHandler eventHandler); /** * un-register an event handler * @param eventHandler */ void unRegister(EventHandler eventHandler); /** * un-register event handler whose classLoader matches the specified param. * @param classLoader */ void unRegister(ClassLoader classLoader); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/event/EventHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.event; import com.alipay.sofa.ark.spi.event.ArkEvent; import com.alipay.sofa.ark.spi.service.PriorityOrdered; /** * @author qilong.zql * @since 0.4.0 */ public interface EventHandler extends PriorityOrdered { /** * Called by the {@link EventAdminService} service to notify the listener of an * event. * * @param event The event that occurred. */ void handleEvent(E event); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/extension/ArkServiceLoader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.extension; import java.util.List; /** * @author qilong.zql * @since 0.6.0 */ public class ArkServiceLoader { private static ExtensionLoaderService extensionLoaderService; public static T loadExtensionFromArkPlugin(Class interfaceType, String extensionName, String pluginName) { return extensionLoaderService.getExtensionContributorFromArkPlugin(interfaceType, extensionName, pluginName); } public static T loadExtensionFromArkBiz(Class interfaceType, String extensionName, String bizIdentity) { return extensionLoaderService.getExtensionContributorFromArkBiz(interfaceType, extensionName, bizIdentity); } public static List loadExtensionsFromArkBiz(Class interfaceType, String bizIdentity) { return extensionLoaderService .getExtensionContributorsFromArkBiz(interfaceType, bizIdentity); } public static ExtensionLoaderService getExtensionLoaderService() { return extensionLoaderService; } public static void setExtensionLoaderService(ExtensionLoaderService extensionLoaderService) { ArkServiceLoader.extensionLoaderService = extensionLoaderService; } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/extension/Extensible.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.extension; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * annotation required on interface which is marked as extensible. * * @author qilong.zql * @since 0.6.0 */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Extensible { /** * return specify extensible file name, default value is the * full name of interface. */ String file() default ""; /** * return whether this a singleton, with a single, shared instance * returned on all calls, default value is true. */ boolean singleton() default true; } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/extension/Extension.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.extension; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Annotation required on the implementation of extensible interface. * * @author qilong.zql * @since 0.6.0 */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Extension { /** * extension name */ String value() default ""; /** * extension order, Higher values are interpreted as lower priority. * As a consequence, the object with the lowest value has the highest * priority. */ int order() default 100; } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/extension/ExtensionClass.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.extension; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.service.PriorityOrdered; import java.util.Objects; /** * @author qilong.zql * @since 0.6.0 */ public class ExtensionClass implements PriorityOrdered { /** * extensible interface type */ private Class interfaceClass; /** * extension implementation type */ private Class implementClass; /** * annotation on {@link ExtensionClass#interfaceClass} */ private Extensible extensible; /** * annotation on {@link ExtensionClass#implementClass} */ private Extension extension; /** * where extension implementation is defined, ark plugin or ark biz. * now it only support ark plugin. */ private T definedLocation; /** * if extensible interface type is singleton, return this as extension implementation. */ private I singleton; public Class getInterfaceClass() { return interfaceClass; } public void setInterfaceClass(Class interfaceClass) { this.interfaceClass = interfaceClass; } public Class getImplementClass() { return implementClass; } public void setImplementClass(Class implementClass) { this.implementClass = implementClass; } public Extensible getExtensible() { return extensible; } public void setExtensible(Extensible extensible) { this.extensible = extensible; } public Extension getExtension() { return extension; } public void setExtension(Extension extension) { this.extension = extension; } public T getDefinedLocation() { return definedLocation; } public void setDefinedLocation(T definedLocation) { this.definedLocation = definedLocation; } public I getSingleton() { if (singleton == null) { synchronized (this) { singleton = newInstance(); } } return singleton; } public I getObject() { if (extensible.singleton()) { return getSingleton(); } else { return newInstance(); } } private I newInstance() { try { return implementClass.newInstance(); } catch (Throwable throwable) { throw new ArkRuntimeException(String.format("Create %s instance error.", implementClass.getCanonicalName()), throwable); } } @Override public int getPriority() { return extension.order(); } @Override public boolean equals(Object o) { return this == o; } @Override public int hashCode() { return Objects.hash(interfaceClass, implementClass, extensible, extension, definedLocation, singleton); } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/extension/ExtensionLoaderService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.extension; import java.util.List; /** * @author qilong.zql * @since 0.6.0 */ public interface ExtensionLoaderService { /** * get specified extension implementation which match interfaceType and extensionName from ark plugin * * @param interfaceType extensible interface type * @param extensionName extension name * @param extension implementation type * @param pluginName pluginName * @return */ T getExtensionContributorFromArkPlugin(Class interfaceType, String extensionName, String pluginName); /** * get specified extension implementation which match interfaceType and extensionName from ark biz * * @param interfaceType extensible interface type * @param extensionName extension name * @param extension implementation type * @param bizIdentity bizIdentity * @return */ T getExtensionContributorFromArkBiz(Class interfaceType, String extensionName, String bizIdentity); /** * get specified extension implementation which match interfaceType and extensionName from ark biz * * @param interfaceType extensible interface type * @param extension implementation type * @param bizIdentity bizIdentity * @return */ List getExtensionContributorsFromArkBiz(Class interfaceType, String bizIdentity); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/injection/InjectionService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.injection; import com.alipay.sofa.ark.spi.registry.ServiceReference; /** * Process annotation {@link com.alipay.sofa.ark.spi.service.ArkInject} * * @author qilong.zql * @since 0.4.0 */ public interface InjectionService { /** * auto inject Ark Service * @param reference */ void inject(ServiceReference reference); /** * inject field annotated by {@literal com.alipay.sofa.ark.spi.service.ArkInject} * @param object */ void inject(Object object); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/plugin/PluginDeployService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.plugin; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.service.ArkService; /** * Service to deploy ark plugin * * @author ruoshan * @since 0.1.0 */ public interface PluginDeployService extends ArkService { /** * Deploy all ark plugins * @throws ArkRuntimeException * @since 0.1.0 */ void deploy() throws ArkRuntimeException; /** * Un-deploy all ark plugins * @throws ArkRuntimeException */ void unDeploy() throws ArkRuntimeException; } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/plugin/PluginFactoryService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.plugin; import com.alipay.sofa.ark.spi.archive.PluginArchive; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.model.PluginConfig; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.Set; /** * Create Plugin according to {@link File} and {@link PluginArchive} * * @author qilong.zql * @since 0.4.0 */ public interface PluginFactoryService { /** * Create Plugin Model according to {@link PluginArchive} * * @param pluginArchive the {@link PluginArchive} model * @return Biz * @throws IOException throw io exception when {@link PluginArchive} is invalid. */ Plugin createPlugin(PluginArchive pluginArchive) throws IOException; /** * can custom plugin extensions urls * @param pluginArchive * @param extensions * @param exportPackages * @return * @throws IOException */ Plugin createPlugin(PluginArchive pluginArchive, URL[] extensions, Set exportPackages) throws IOException; /** * Create Plugin Model according to {@link File} * * @param file the plugin file * @return Plugin * @throws IOException throw io exception when {@link PluginArchive} is invalid. */ Plugin createPlugin(File file) throws IOException; Plugin createPlugin(File file, URL[] extensions) throws IOException; Plugin createPlugin(File file, PluginConfig pluginConfig) throws IOException; /** * Mock Plugin Model according to master biz * @return */ Plugin createEmbedPlugin(PluginArchive pluginArchive, ClassLoader masterClassLoader) throws IOException; } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/plugin/PluginManagerService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.plugin; import com.alipay.sofa.ark.spi.model.Plugin; import java.util.List; import java.util.Set; /** * Service to manage ark plugin * * @author ruoshan * @since 0.1.0 */ public interface PluginManagerService { /** * Register ark plugin * @param plugin ark plugin info */ void registerPlugin(Plugin plugin); /** * Get plugin info by plugin name * @param pluginName plugin name * @return */ Plugin getPluginByName(String pluginName); /** * Get all plugin names * @return */ Set getAllPluginNames(); /** * Get all plugins in priority PriorityOrdered * @return */ List getPluginsInOrder(); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/registry/RegistryService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.registry; import com.alipay.sofa.ark.spi.registry.ServiceFilter; import com.alipay.sofa.ark.spi.registry.ServiceProvider; import com.alipay.sofa.ark.spi.registry.ServiceReference; import java.util.List; /** * Registry Service Interface * * @author ruoshan * @since 0.1.0 */ public interface RegistryService { /** * Publish Service * @param ifClass service interface * @param implObject service implement object * @param serviceProvider service provider * @param * @return */ ServiceReference publishService(Class ifClass, T implObject, ServiceProvider serviceProvider); /** * Publish Service * @param ifClass service interface * @param implObject service implement object * @param uniqueId service implementation unique-id * @param serviceProvider service provider * @param * @return */ ServiceReference publishService(Class ifClass, T implObject, String uniqueId, ServiceProvider serviceProvider); /** * Get Service, when there are multiple services, return the highest priority service * {@link com.alipay.sofa.ark.spi.service.PriorityOrdered} * * @param ifClass service interface * @param * @return service reference */ ServiceReference referenceService(Class ifClass); /** * Get Service, when there are multiple services, return the highest priority service * {@link com.alipay.sofa.ark.spi.service.PriorityOrdered} * * @param ifClass service interface * @param uniqueId service implementation unique-id * @param * @return service reference */ ServiceReference referenceService(Class ifClass, String uniqueId); /** * Get Service List, ordered by priority. * {@link com.alipay.sofa.ark.spi.service.PriorityOrdered} * * @param ifClass service interface * @param * @return service reference list */ List> referenceServices(Class ifClass); /** * Get Service List, ordered by priority. * {@link com.alipay.sofa.ark.spi.service.PriorityOrdered} * * @param ifClass service interface * @param uniqueId service unique-id * @param * @return service reference list */ List> referenceServices(Class ifClass, String uniqueId); /** * Get Service List, ordered by priority. * {@link com.alipay.sofa.ark.spi.service.PriorityOrdered} * * @param serviceFilter service filter * @return service reference */ List> referenceServices(ServiceFilter serviceFilter); /** * Drive out service which match the given serviceFilter. * * @param serviceFilter * @return return the count of deleted services */ int unPublishServices(ServiceFilter serviceFilter); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/session/CommandProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.session; /** * Answer a string (may be as many lines as you like) with help * texts that explain the command. * * @author qilong.zql * @since 0.4.0 */ public interface CommandProvider { /** * Get Command Help Message Tips * @return */ String getHelp(); /** * Handler Specified Command * @param command * @return */ String handleCommand(String command); /** * Validate whether command is valid * @param command * @return */ boolean validate(String command); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/session/TelnetServerService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.session; import com.alipay.sofa.ark.spi.service.ArkService; /** * Providers a thread that listens on the port for telnet connections * * @author qilong.zql * @since 0.4.0 */ public interface TelnetServerService extends ArkService { void run(); void shutdown(); } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/web/AbstractEmbeddedServerService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.web; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public abstract class AbstractEmbeddedServerService implements EmbeddedServerService { private Map servers = new ConcurrentHashMap<>(); @Override public T getEmbedServer(int port) { return servers.get(port); } @Override public boolean putEmbedServer(int port, T container) { if (container == null) { return false; } return servers.putIfAbsent(port, container) == null; } @Override public Iterator iterator() { return servers.values().iterator(); } } ================================================ FILE: sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/web/EmbeddedServerService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.web; /** * Fetch embed tomcat container in ark * * @author qilong.zql * @since 0.6.0 */ public interface EmbeddedServerService extends Iterable { /** * get embed tomcat with port. * @return */ T getEmbedServer(int port); /** * put embed tomcat with port. * Once web container instance (e.g. Tomcat, Netty) set to this EmbeddedServerService, it is usually can not be modified! * @param port server port * @param container server container */ boolean putEmbedServer(int port, T container); } ================================================ FILE: sofa-ark-parent/core/spi/src/test/java/com/alipay/sofa/ark/spi/argument/LaunchCommandTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.argument; import com.alipay.sofa.ark.exception.ArkRuntimeException; import org.junit.Before; import org.junit.Test; import java.io.File; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import java.util.Objects; import static com.alipay.sofa.ark.spi.argument.CommandArgument.*; import static com.alipay.sofa.ark.spi.argument.LaunchCommand.parse; import static java.lang.Integer.valueOf; import static java.lang.String.format; import static java.net.URLDecoder.decode; import static org.junit.Assert.*; /** * @author qilong.zql 18/3/9 */ public class LaunchCommandTest { public static List arkCommand = new ArrayList(); public static int count; String classpath; Method method; URL fatJarUrl; @Before public void init() { try { classpath = getClasspath(Objects.requireNonNull(getURLs(this.getClass() .getClassLoader()))); method = MainClass.class.getMethod("main", String[].class); fatJarUrl = this.getClass().getClassLoader().getResource("test 2.jar"); } catch (Exception ex) { throw new RuntimeException(ex); } arkCommand.add(format("%s%s=%s", ARK_CONTAINER_ARGUMENTS_MARK, FAT_JAR_ARGUMENT_KEY, fatJarUrl)); arkCommand.add(format("%s%s=%s", ARK_CONTAINER_ARGUMENTS_MARK, CLASSPATH_ARGUMENT_KEY, classpath)); arkCommand.add(format("%s%s=%s", ARK_BIZ_ARGUMENTS_MARK, ENTRY_CLASS_NAME_ARGUMENT_KEY, method.getDeclaringClass().getName())); arkCommand.add(format("%s%s=%s", ARK_BIZ_ARGUMENTS_MARK, ENTRY_METHOD_NAME_ARGUMENT_KEY, method.getName())); LaunchCommandTest.count = 0; } @Test public void testCommandParser() { try { List args = new ArrayList<>(); args.addAll(arkCommand); args.add("p1"); args.add("p2"); LaunchCommand launchCommand = parse(args.toArray(new String[] {})); assertTrue(launchCommand.getEntryClassName().equals( method.getDeclaringClass().getName())); assertTrue(launchCommand.getEntryMethodName().equals(method.getName())); assertTrue(launchCommand.getExecutableArkBizJar().equals(fatJarUrl)); assertTrue(launchCommand.getClasspath().length == classpath.split(CLASSPATH_SPLIT).length); for (URL url : launchCommand.getClasspath()) { assertTrue(classpath.contains(url.toExternalForm())); } assertTrue(2 == launchCommand.getLaunchArgs().length); } catch (Exception ex) { assertNull(ex); } } @Test public void testEncodedURL() { File file = new File(fatJarUrl.getFile()); assertFalse(file.exists()); file = new File(decode(fatJarUrl.getFile())); assertTrue(file.exists()); } public static class MainClass { public static void main(String[] args) { if (args.length > 0) { LaunchCommandTest.count += valueOf(args[0]); } } } private String getClasspath(URL[] urls) { StringBuilder sb = new StringBuilder(); for (URL url : urls) { sb.append(url.toExternalForm()).append(CLASSPATH_SPLIT); } return sb.toString(); } private URL[] getURLs(ClassLoader classLoader) { // https://stackoverflow.com/questions/46519092/how-to-get-all-jars-loaded-by-a-java-application-in-java9 if (classLoader instanceof URLClassLoader) { return ((URLClassLoader) classLoader).getURLs(); } // support jdk9+ String classpath = System.getProperty("java.class.path"); String[] classpathEntries = classpath.split(System.getProperty("path.separator")); List classpathURLs = new ArrayList<>(); for (String classpathEntry : classpathEntries) { URL url = null; try { url = new File(classpathEntry).toURI().toURL(); } catch (MalformedURLException e) { e.printStackTrace(); throw new ArkRuntimeException("Failed to get urls from " + classLoader, e); } classpathURLs.add(url); } return classpathURLs.toArray(new URL[0]); } @Test public void testOtherMethods() throws MalformedURLException { List args = new ArrayList<>(); args.addAll(arkCommand); args.add("p1"); args.add("p2"); LaunchCommand launchCommand = parse(args.toArray(new String[] {})); launchCommand.setProfiles(new String[] { "1" }); assertArrayEquals(new String[] { "1" }, launchCommand.getProfiles()); assertEquals("ab", LaunchCommand.toString(new String[] { "a", "b" })); } } ================================================ FILE: sofa-ark-parent/core/spi/src/test/java/com/alipay/sofa/ark/spi/constant/ConstantsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.constant; import org.junit.Test; import java.util.ArrayList; import java.util.List; import static com.alipay.sofa.ark.spi.constant.Constants.*; import static java.lang.Class.forName; import static org.junit.Assert.assertEquals; public class ConstantsTest { @Test public void testAllMethods() throws Exception { forName("com.alipay.sofa.ark.spi.constant.Constants"); List channelQuits = new ArrayList<>(); channelQuits.add("quit"); channelQuits.add("q"); channelQuits.add("exit"); assertEquals(channelQuits, CHANNEL_QUIT); assertEquals(new String(new byte[] { (byte) 13, (byte) 10 }), TELNET_STRING_END); assertEquals("", DEFAULT_PROFILE); } } ================================================ FILE: sofa-ark-parent/core/spi/src/test/java/com/alipay/sofa/ark/spi/ext/ExtResponseTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.ext; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.springframework.beans.BeanUtils.copyProperties; public class ExtResponseTest { @Test public void testAllMethods() { copyProperties(new ExtResponse<>(), new ExtResponse<>()); ExtResponse extResponse = new ExtResponse<>(); extResponse.setSuccess(true); extResponse.setErrorMsg("myMsg"); extResponse.setErrorCode("001"); extResponse.setData("myData"); String extResponseStr = extResponse.toString(); assertEquals("ExtResponse{" + "success=true, errorMsg='myMsg'" + ", errorCode='001'" + ", data=myData}", extResponseStr); } } ================================================ FILE: sofa-ark-parent/core/spi/src/test/java/com/alipay/sofa/ark/spi/model/BizOperationTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.model; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import org.junit.Test; import static com.alipay.sofa.ark.spi.model.BizOperation.createBizOperation; import static com.alipay.sofa.ark.spi.model.BizState.*; import static org.junit.Assert.*; import static org.springframework.beans.BeanUtils.copyProperties; public class BizOperationTest { @Test public void testOperationEqual() { BizOperation op1 = createBizOperation(); op1.setBizName("biz A").setBizVersion("1.0.0") .setOperationType(BizOperation.OperationType.INSTALL); BizOperation op2 = createBizOperation(); op2.setBizName("biz A").setBizVersion("1.0.0") .setOperationType(BizOperation.OperationType.INSTALL); assertEquals(op1, op1); assertEquals(op1, op2); op2.setOperationType(BizOperation.OperationType.UNINSTALL); assertNotEquals(op1, op2); op2.setBizVersion("2.0.0"); assertNotEquals(op1, op2); op2.setBizName("biz B"); assertNotEquals(op1, op2); assertFalse(op1.equals("")); assertFalse(op1.equals(null)); } @Test public void testAbstractArkEvent() { AbstractArkEvent abstractArkEvent = new AbstractArkEvent("") { }; copyProperties(abstractArkEvent, abstractArkEvent); } @Test public void testBizState() { assertEquals(UNRESOLVED, BizState.of("UNRESOLVED")); assertEquals(RESOLVED, BizState.of("RESOLVED")); assertEquals(ACTIVATED, BizState.of("ACTIVATED")); assertEquals(DEACTIVATED, BizState.of("DEACTIVATED")); assertEquals(BROKEN, BizState.of("aaa")); } } ================================================ FILE: sofa-ark-parent/core/spi/src/test/java/com/alipay/sofa/ark/spi/replay/ReplayContextTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.replay; import org.junit.Test; import static com.alipay.sofa.ark.spi.replay.ReplayContext.*; import static org.junit.Assert.assertEquals; public class ReplayContextTest { @Test public void testAllMethods() { assertEquals(null, get()); set("0.0"); assertEquals("0.0", get()); unset(); assertEquals(null, get()); setPlaceHolder(); assertEquals(null, get()); set("2.0"); setPlaceHolder(); assertEquals(PLACEHOLDER, get()); clearPlaceHolder(); assertEquals("2.0", get()); clearPlaceHolder(); assertEquals("2.0", get()); } } ================================================ FILE: sofa-ark-parent/core/spi/src/test/java/com/alipay/sofa/ark/spi/service/extension/ExtensionClassTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.spi.service.extension; import org.junit.Test; import static org.springframework.beans.BeanUtils.copyProperties; public class ExtensionClassTest { @Test public void testExtensionClassTest() { ExtensionClass extensionClass = new ExtensionClass(); copyProperties(extensionClass, extensionClass); extensionClass.equals(extensionClass); } } ================================================ FILE: sofa-ark-parent/core/spi/src/test/resources/test 2.jar ================================================ ================================================ FILE: sofa-ark-parent/core-impl/archive/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-archive` **Package**: `com.alipay.sofa.ark.loader` This module implements archive handling for SOFAArk - parsing and loading JAR files, directories, and embedded archives. ## Purpose - Parse Ark package structure (Fat Jar) - Load Biz and Plugin archives from JAR files or directories - Handle executable Ark JAR format ## Key Classes ### Archive Implementations - `ExecutableArkBizJar` - Main entry point for executable Ark JAR - `JarBizArchive` - Business module from JAR file - `JarPluginArchive` - Plugin from JAR file - `JarContainerArchive` - Container archive from JAR - `DirectoryBizArchive` - Business module from exploded directory - `ExplodedBizArchive` - Exploded JAR directory archive - `DirectoryContainerArchive` - Container from directory - `EmbedClassPathArchive` - Embedded classpath archive for testing ### Archive Utilities - `archive.JarFileArchive` - Spring Boot style JAR archive - `archive.ExplodedArchive` - Exploded directory archive - `jar.*` - JAR file handling utilities ## Archive Structure The Ark Fat Jar structure follows Spring Boot executable JAR format: ``` ark-executable.jar ├── SOFA-ARK/ │ ├── biz/ # Business module JARs │ └── plugin/ # Plugin JARs ├── SOFA-ARK-CONTAINER/ # Container JAR └── BOOT-INF/ # Application classes ``` ## Dependencies - `sofa-ark-spi` - Archive interfaces - `sofa-ark-common` - Utilities - `spring-boot-loader` - Spring Boot loader for JAR handling ## Used By - `sofa-ark-container` - Uses archives to load Biz and Plugins - `ark-maven-plugin` - Creates the archive structure ================================================ FILE: sofa-ark-parent/core-impl/archive/pom.xml ================================================ 4.0.0 sofa-ark-core-impl com.alipay.sofa ${sofa.ark.version} sofa-ark-archive ${project.groupId}:${project.artifactId} com.alipay.sofa sofa-ark-spi com.alipay.sofa sofa-ark-api com.alipay.sofa sofa-ark-common commons-io commons-io org.apache.maven maven-model junit junit test org.mockito mockito-inline ${mockito.version} test org.springframework.boot spring-boot-loader test ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/bootstrap/AbstractLauncher.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.bootstrap; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.loader.jar.JarFile; import com.alipay.sofa.ark.spi.archive.ContainerArchive; import com.alipay.sofa.ark.spi.archive.ExecutableArchive; import com.alipay.sofa.ark.spi.argument.CommandArgument; import com.alipay.sofa.ark.spi.constant.Constants; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @author qilong.zql * @since 0.1.0 */ public abstract class AbstractLauncher { /** * Launch the ark container. This method is the initial entry point when execute an fat jar. * @throws Exception if the ark container fails to launch. */ public Object launch(String[] args) throws Exception { if (!isEmbedEnable()) { JarFile.registerUrlProtocolHandler(); } ClassLoader classLoader = createContainerClassLoader(getContainerArchive()); List attachArgs = new ArrayList<>(); attachArgs .add(String.format("%s%s=%s", CommandArgument.ARK_CONTAINER_ARGUMENTS_MARK, CommandArgument.FAT_JAR_ARGUMENT_KEY, getExecutableArchive().getUrl() .toExternalForm())); attachArgs.addAll(Arrays.asList(args)); return launch(attachArgs.toArray(new String[attachArgs.size()]), getMainClass(), classLoader); } /** * Launch the ark container. This method is the initial entry point when execute in IDE. * @throws Exception if the ark container fails to launch. */ public Object launch(String[] args, String classpath, Method method) throws Exception { if (!isEmbedEnable()) { JarFile.registerUrlProtocolHandler(); } ClassLoader classLoader = createContainerClassLoader(getContainerArchive()); List attachArgs = new ArrayList<>(); attachArgs.add(String.format("%s%s=%s", CommandArgument.ARK_CONTAINER_ARGUMENTS_MARK, CommandArgument.CLASSPATH_ARGUMENT_KEY, classpath)); attachArgs.add(String.format("%s%s=%s", CommandArgument.ARK_BIZ_ARGUMENTS_MARK, CommandArgument.ENTRY_CLASS_NAME_ARGUMENT_KEY, method.getDeclaringClass().getName())); attachArgs.add(String.format("%s%s=%s", CommandArgument.ARK_BIZ_ARGUMENTS_MARK, CommandArgument.ENTRY_METHOD_NAME_ARGUMENT_KEY, method.getName())); attachArgs.addAll(Arrays.asList(args)); return launch(attachArgs.toArray(new String[attachArgs.size()]), getMainClass(), classLoader); } /** * Launch the ark container in {@literal TEST} run mode. * * @param classpath classpath of ark-biz * @param testClass test class * @return Object {@literal com.alipay.sofa.ark.container.ArkContainer} * @throws Exception */ public Object launch(String classpath, Class testClass) throws Exception { if (!isEmbedEnable()) { JarFile.registerUrlProtocolHandler(); } ClassLoader classLoader = createContainerClassLoader(getContainerArchive()); List attachArgs = new ArrayList<>(); attachArgs.add(String.format("%s%s=%s", CommandArgument.ARK_CONTAINER_ARGUMENTS_MARK, CommandArgument.CLASSPATH_ARGUMENT_KEY, classpath)); attachArgs.add(String.format("%s%s=%s", CommandArgument.ARK_BIZ_ARGUMENTS_MARK, CommandArgument.ENTRY_CLASS_NAME_ARGUMENT_KEY, testClass.getCanonicalName())); return launch(attachArgs.toArray(new String[attachArgs.size()]), getMainClass(), classLoader); } protected Object launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception { ClassLoader old = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(classLoader); return createMainMethodRunner(mainClass, args).run(); } finally { Thread.currentThread().setContextClassLoader(old); } } protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args) { return new MainMethodRunner(mainClass, args, null); } /** * Returns the executable archive * @return the executable archive * @throws Exception */ protected abstract ExecutableArchive getExecutableArchive() throws Exception; /** * Returns the container archive archive * @return the container archive archive * @throws Exception */ protected ContainerArchive getContainerArchive() throws Exception { return getExecutableArchive().getContainerArchive(); } /** * Create a classloader for the ark container archive * @param containerArchive the ark container archive * @return the classloader load ark container * @throws Exception */ protected ClassLoader createContainerClassLoader(ContainerArchive containerArchive) throws Exception { List classpath = getExecutableArchive().getConfClasspath(); classpath.addAll(Arrays.asList(containerArchive.getUrls())); URL[] agentUrls = ClassLoaderUtils.getAgentClassPath(); // set parent as app class loader to ensure agent like skywalking works well ClassLoader agentClassLoader = (agentUrls.length > 0 ? new AgentClassLoader(agentUrls, Thread.currentThread().getContextClassLoader()) : null); return createContainerClassLoader(classpath.toArray(new URL[] {}), null, agentClassLoader); } /** * Create a classloader for the specified URLs. * @param urls the URLs * @param parent the parent * @return the classloader load ark container */ protected ClassLoader createContainerClassLoader(URL[] urls, ClassLoader parent, ClassLoader agentClassLoader) { return isEmbedEnable() ? new ContainerClassLoader(urls, parent, this.getClass() .getClassLoader(), agentClassLoader) : new ContainerClassLoader(urls, parent, agentClassLoader); } private boolean isEmbedEnable() { return Boolean.getBoolean(Constants.EMBED_ENABLE); } /** * Returns the main class that should be launched. * * @return the name of the main class * @throws Exception if the main class cannot be obtained. */ protected abstract String getMainClass() throws Exception; } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/bootstrap/AgentClassLoader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.bootstrap; import java.net.URL; import java.net.URLClassLoader; /** * Used to collect classpath of java agent. * * @author qilong.zql * @since 0.6.0 */ public class AgentClassLoader extends URLClassLoader { static { ClassLoader.registerAsParallelCapable(); } public AgentClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/bootstrap/ArkLauncher.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.bootstrap; import com.alipay.sofa.ark.spi.archive.ExecutableArchive; /** * {@link AbstractLauncher} for JAR based archives. This launcher assumes that dependency jars are * included inside a {@code /SOFA-ARK/container/lib} directory and that application fat jar * are included inside a {@code /SOFA-ARK/biz/} directory and that ark plugin fat jar are included * inside a {@code /SOFA-ARK/plugin/} directory * * @author qilong.zql * @since 0.1.0 */ public class ArkLauncher extends BaseExecutableArchiveLauncher { public final String SOFA_ARK_MAIN = "com.alipay.sofa.ark.container.ArkContainer"; public static void main(String[] args) throws Exception { new ArkLauncher().launch(args); } public ArkLauncher() { } public ArkLauncher(ExecutableArchive executableArchive) { super(executableArchive); } @Override protected String getMainClass() { return SOFA_ARK_MAIN; } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/bootstrap/BaseExecutableArchiveLauncher.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.bootstrap; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.loader.ExecutableArkBizJar; import com.alipay.sofa.ark.loader.archive.ExplodedArchive; import com.alipay.sofa.ark.loader.archive.JarFileArchive; import com.alipay.sofa.ark.spi.archive.ExecutableArchive; import java.io.File; import java.net.URI; import java.security.CodeSource; import java.security.ProtectionDomain; /** * Base class for executable archive {@link AbstractLauncher}s. * * @author qilong.zql * @since 0.1.0 */ public abstract class BaseExecutableArchiveLauncher extends AbstractLauncher { private final ExecutableArchive executableArchive; public BaseExecutableArchiveLauncher() { try { this.executableArchive = createArchive(); } catch (Exception ex) { throw new IllegalStateException(ex); } } protected BaseExecutableArchiveLauncher(ExecutableArchive executableArchive) { this.executableArchive = executableArchive; } @Override protected ExecutableArchive getExecutableArchive() { return this.executableArchive; } /** * Returns the executable file archive * @return executable file archive * @throws Exception */ protected ExecutableArchive createArchive() throws Exception { ProtectionDomain protectionDomain = getClass().getProtectionDomain(); CodeSource codeSource = protectionDomain.getCodeSource(); URI location = (codeSource == null ? null : codeSource.getLocation().toURI()); String path = (location == null ? null : location.getSchemeSpecificPart()); if (path == null) { throw new IllegalStateException("Unable to determine code source archive"); } File root = FileUtils.file(path); if (!root.exists()) { throw new IllegalStateException("Unable to determine code source archive from " + root); } return root.isDirectory() ? new ExecutableArkBizJar(new ExplodedArchive(root)) : new ExecutableArkBizJar(new JarFileArchive(root), root.toURI().toURL()); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/bootstrap/ClasspathLauncher.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.bootstrap; import com.alipay.sofa.ark.common.util.*; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.loader.*; import com.alipay.sofa.ark.loader.archive.JarFileArchive; import com.alipay.sofa.ark.spi.archive.*; import com.alipay.sofa.ark.spi.constant.Constants; import java.io.*; import java.net.URL; import java.net.URLClassLoader; import java.util.*; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import static com.alipay.sofa.ark.spi.constant.Constants.ARK_CONF_BASE_DIR; import static com.alipay.sofa.ark.spi.constant.Constants.SUREFIRE_BOOT_CLASSPATH; import static com.alipay.sofa.ark.spi.constant.Constants.SUREFIRE_BOOT_CLASSPATH_SPLIT; /** * @author ruoshan * @since 0.1.0 */ public class ClasspathLauncher extends ArkLauncher { public ClasspathLauncher(ClassPathArchive classPathArchive) { super(classPathArchive); } public static class ClassPathArchive implements ExecutableArchive { public static final String FILE_IN_JAR = "!/"; private final String className; private final String methodName; private final URL[] urls; protected final URLClassLoader urlClassLoader; private File arkConfBaseDir; public ClassPathArchive(String className, String methodName, URL[] urls) throws IOException { AssertUtils.isFalse(StringUtils.isEmpty(className), "Entry class name must be specified."); this.className = className; this.methodName = methodName; this.urls = urls; List classpath = getConfClasspath(); classpath.addAll(Arrays.asList(this.urls)); urlClassLoader = new URLClassLoader(classpath.toArray(new URL[] {}), null); } public List filterUrls(String resource) throws Exception { List urlList = new ArrayList<>(); Enumeration enumeration = urlClassLoader.findResources(resource); while (enumeration.hasMoreElements()) { URL resourceUrl = enumeration.nextElement(); String resourceFile = resourceUrl.getFile(); String jarFile = resourceFile.substring(0, resourceFile.length() - resource.length() - FILE_IN_JAR.length()); urlList.add(new URL(jarFile)); } return urlList; } @Override public ContainerArchive getContainerArchive() throws Exception { ContainerArchive archive = getJarContainerArchive(); if (archive == null) { archive = createDirectoryContainerArchive(); } if (archive == null) { throw new ArkRuntimeException("No Ark Container Jar File Found."); } return archive; } protected ContainerArchive getJarContainerArchive() throws Exception { List urlList = filterUrls(Constants.ARK_CONTAINER_MARK_ENTRY); if (urlList.isEmpty()) { return null; } if (urlList.size() > 1) { throw new ArkRuntimeException("Duplicate Container Jar File Found."); } return new JarContainerArchive(new JarFileArchive(FileUtils.file(urlList.get(0) .getFile()))); } @Override public List getBizArchives() throws Exception { List urlList = filterUrls(Constants.ARK_BIZ_MARK_ENTRY); List bizArchives = new LinkedList<>(); if (className != null && methodName != null) { bizArchives.add(createDirectoryBizModuleArchive()); } for (URL url : urlList) { bizArchives .add(new JarBizArchive(new JarFileArchive(FileUtils.file(url.getFile())))); } return bizArchives; } @Override public List getPluginArchives() throws Exception { List urlList = filterUrls(Constants.ARK_PLUGIN_MARK_ENTRY); List pluginArchives = new ArrayList<>(); for (URL url : urlList) { pluginArchives.add(new JarPluginArchive(new JarFileArchive(FileUtils.file(url .getFile())))); } return pluginArchives; } @Override public List getConfClasspath() throws IOException { List urls = new ArrayList<>(); if (arkConfBaseDir == null) { arkConfBaseDir = deduceArkConfBaseDir(); } scanConfClasspath(arkConfBaseDir, urls); return urls; } private void scanConfClasspath(File arkConfBaseDir, List classpath) throws IOException { if (arkConfBaseDir == null || arkConfBaseDir.isFile() || arkConfBaseDir.listFiles() == null) { return; } classpath.add(arkConfBaseDir.toURI().toURL()); for (File subFile : arkConfBaseDir.listFiles()) { scanConfClasspath(subFile, classpath); } } private File deduceArkConfBaseDir() { File arkConfDir = null; try { URLClassLoader tempClassLoader = new URLClassLoader(urls); Class entryClass = tempClassLoader.loadClass(className); String classLocation = ClassUtils.getCodeBase(entryClass); if (classLocation.startsWith("file:")) { classLocation = classLocation.substring("file:".length()); } File file = classLocation == null ? null : FileUtils.file(classLocation); while (file != null) { arkConfDir = FileUtils .file(file.getPath() + File.separator + ARK_CONF_BASE_DIR); if (arkConfDir.exists() && arkConfDir.isDirectory()) { break; } file = file.getParentFile(); } } catch (Throwable throwable) { throw new ArkRuntimeException(throwable); } // return 'conf/' directory or null return arkConfDir == null ? null : arkConfDir.getParentFile(); } @Override public URL getUrl() { throw new RuntimeException("unreachable invocation."); } @Override public Manifest getManifest() { throw new RuntimeException("unreachable invocation."); } @Override public List getNestedArchives(EntryFilter filter) { throw new RuntimeException("unreachable invocation."); } @Override public Archive getNestedArchive(Entry entry) { throw new RuntimeException("unreachable invocation."); } @Override public InputStream getInputStream(ZipEntry zipEntry) { throw new RuntimeException("unreachable invocation."); } @Override public Iterator iterator() { throw new RuntimeException("unreachable invocation."); } protected BizArchive createDirectoryBizModuleArchive() { return new DirectoryBizArchive(className, methodName, filterBizUrls(urls)); } protected ContainerArchive createDirectoryContainerArchive() { URL[] candidates; if (fromSurefire(urls)) { candidates = parseClassPathFromSurefireBoot(getSurefireBooterJar(urls)); } else { candidates = urls; } URL[] filterUrls = filterURLs(candidates); return filterUrls == null ? null : new DirectoryContainerArchive(filterUrls); } protected boolean fromSurefire(URL[] urls) { if (urls.length <= 4) { for (URL url : urls) { if (url.getFile().contains(Constants.SUREFIRE_BOOT_JAR)) { return true; } } } return false; } private URL getSurefireBooterJar(URL[] urls) { for (URL url : urls) { if (url.getFile().contains(Constants.SUREFIRE_BOOT_JAR)) { return url; } } return null; } /** * this method is used to choose jar file which is contained in sofa-ark-all.jar * * @return */ protected URL[] filterURLs(URL[] urls) { Set arkContainerJarMarkers = DirectoryContainerArchive .getArkContainerJarMarkers(); Set containerClassPath = new HashSet<>(); for (String marker : arkContainerJarMarkers) { for (URL url : urls) { if (url.getPath().contains(marker)) { containerClassPath.add(url); } } } return arkContainerJarMarkers.size() != containerClassPath.size() ? null : containerClassPath.toArray(new URL[] {}); } /** * this method is used to eliminate agent classpath and biz classpath * * @param urls * @return */ protected URL[] filterBizUrls(URL[] urls) { URL[] agentClassPath = ClassLoaderUtils.getAgentClassPath(); List urlList; try { urlList = filterUrls(Constants.ARK_BIZ_MARK_ENTRY); } catch (Throwable throwable) { // ignore urlList = Collections.emptyList(); } List bizURls = new ArrayList<>(); boolean isAgent; for (URL url : urls) { isAgent = false; for (URL agentUrl : agentClassPath) { if (url.equals(agentUrl)) { isAgent = true; break; } } if (!isAgent && !urlList.contains(url)) { bizURls.add(url); } } return bizURls.toArray(new URL[] {}); } /** * when execute mvn test, the classpath would be recorded in a MANIFEST.MF file , * including a surefire boot jar. * * @param surefireBootJar * @return */ protected URL[] parseClassPathFromSurefireBoot(URL surefireBootJar) { AssertUtils.assertNotNull(surefireBootJar, "SurefireBooter jar should not be null."); try (JarFile jarFile = new JarFile(surefireBootJar.getFile())) { String[] classPath = jarFile.getManifest().getMainAttributes() .getValue(SUREFIRE_BOOT_CLASSPATH).split(SUREFIRE_BOOT_CLASSPATH_SPLIT); List urls = new ArrayList<>(); for (String path : classPath) { urls.add(new URL(path)); } return urls.toArray(new URL[] {}); } catch (IOException ex) { throw new ArkRuntimeException("Parse classpath failed from surefire boot jar.", ex); } } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/bootstrap/ContainerClassLoader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.bootstrap; import com.alipay.sofa.ark.loader.jar.Handler; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.Enumeration; /** * ClassLoader to load Ark Container * * @author qilong.zql * @since 0.1.0 */ public class ContainerClassLoader extends URLClassLoader { private static final String ARK_SPI_PACKAGES = "com.alipay.sofa.ark.spi"; private static final String ARK_API_PACKAGES = "com.alipay.sofa.ark.api"; private static final String ARK_EXCEPTION_PACKAGES = "com.alipay.sofa.ark.exception"; private static final String ARK_LOG_PACKAGES = "com.alipay.sofa.ark.common.log"; private ClassLoader exportClassLoader; private ClassLoader agentClassLoader; public ContainerClassLoader(URL[] urls, ClassLoader parent, ClassLoader agentClassLoader) { super(urls, parent); this.agentClassLoader = agentClassLoader; } public ContainerClassLoader(URL[] urls, ClassLoader parent, ClassLoader exportClassLoader, ClassLoader agentClassLoader) { super(urls, parent); this.exportClassLoader = exportClassLoader; this.agentClassLoader = agentClassLoader; } @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { Handler.setUseFastConnectionExceptions(true); try { Class clazz = resolveArkExportClass(name); if (clazz != null) { return clazz; } return resolveClass(name, resolve); } finally { Handler.setUseFastConnectionExceptions(false); } } public Class resolveClass(String name, boolean resolve) throws ClassNotFoundException { try { return super.loadClass(name, resolve); } catch (ClassNotFoundException e) { if (agentClassLoader != null) { return agentClassLoader.loadClass(name); } throw e; } } /** * Load ark spi class * * @param name * @return */ protected Class resolveArkExportClass(String name) { if (exportClassLoader != null && isArkExportClass(name)) { try { return exportClassLoader.loadClass(name); } catch (ClassNotFoundException e) { // ignore } } return null; } public boolean isArkExportClass(String className) { return className.startsWith(ARK_SPI_PACKAGES) || className.startsWith(ARK_API_PACKAGES) || className.startsWith(ARK_EXCEPTION_PACKAGES) || className.endsWith(ARK_LOG_PACKAGES); } @Override public URL getResource(String name) { Handler.setUseFastConnectionExceptions(true); try { return super.getResource(name); } finally { Handler.setUseFastConnectionExceptions(false); } } @Override public Enumeration getResources(String name) throws IOException { Handler.setUseFastConnectionExceptions(true); try { return new UseFastConnectionExceptionsEnumeration(super.getResources(name)); } finally { Handler.setUseFastConnectionExceptions(false); } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/bootstrap/MainMethodRunner.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.bootstrap; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; /** * Utility class that is used to call a main method. The class * containing the main method is loaded using the thread context class loader. * * @author qilong.zql * @since 0.1.0 */ public class MainMethodRunner { private final String mainClassName; private final String[] args; private final Map envs; /** * Create a new {@link MainMethodRunner} instance. * @param mainClass the main class * @param args incoming arguments */ public MainMethodRunner(String mainClass, String[] args, Map envs) { this.mainClassName = mainClass; this.args = (args == null ? new String[] {} : args.clone()); this.envs = envs == null ? new HashMap<>() : envs; } public Object run() throws Exception { Class mainClass = Thread.currentThread().getContextClassLoader() .loadClass(this.mainClassName); Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); // TODO: add envs into the tenant of jdk return mainMethod.invoke(null, new Object[] { this.args }); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/bootstrap/UseFastConnectionExceptionsEnumeration.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.bootstrap; import com.alipay.sofa.ark.loader.jar.Handler; import java.net.URL; import java.util.Enumeration; /** * * @author ruoshan * @since 0.1.0 */ public class UseFastConnectionExceptionsEnumeration implements Enumeration { private final Enumeration delegate; public UseFastConnectionExceptionsEnumeration(Enumeration delegate) { this.delegate = delegate; } @Override public boolean hasMoreElements() { Handler.setUseFastConnectionExceptions(true); try { return this.delegate.hasMoreElements(); } finally { Handler.setUseFastConnectionExceptions(false); } } @Override public URL nextElement() { Handler.setUseFastConnectionExceptions(true); try { return this.delegate.nextElement(); } finally { Handler.setUseFastConnectionExceptions(false); } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/DirectoryBizArchive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader; import com.alipay.sofa.ark.spi.archive.Archive; import com.alipay.sofa.ark.spi.archive.BizArchive; import com.alipay.sofa.ark.spi.constant.Constants; import java.io.InputStream; import java.net.URL; import java.util.Iterator; import java.util.List; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import static com.alipay.sofa.ark.spi.constant.Constants.*; /** * Ark Biz Module Directory * * @author qilong.zql * @since 0.1.0 */ public class DirectoryBizArchive implements BizArchive { public static final String MOCK_IDE_ARK_BIZ_NAME = "Startup In IDE"; public static final String MOCK_IDE_ARK_BIZ_VERSION = "Mock version"; public static final String MOCK_IDE_ARK_BIZ_MAIN_CLASS = "Mock Main Class"; public static final String MOCK_IDE_WEB_CONTEXT_PATH = ROOT_WEB_CONTEXT_PATH; public static final int MOCK_IDE_BIZ_STARTUP_PRIORITY = 0; private final String className; private final String methodName; private final URL[] urls; private final Manifest manifest = new Manifest(); public DirectoryBizArchive(String className, String methodName, URL[] urls) { this.className = (className == null ? MOCK_IDE_ARK_BIZ_MAIN_CLASS : className); this.methodName = methodName; manifest.getMainAttributes().putValue(MAIN_CLASS_ATTRIBUTE, this.className); manifest.getMainAttributes().putValue(START_CLASS_ATTRIBUTE, this.className); manifest.getMainAttributes().putValue(PRIORITY_ATTRIBUTE, String.valueOf(MOCK_IDE_BIZ_STARTUP_PRIORITY)); manifest.getMainAttributes().putValue(ARK_BIZ_NAME, MOCK_IDE_ARK_BIZ_NAME); manifest.getMainAttributes().putValue(ARK_BIZ_VERSION, MOCK_IDE_ARK_BIZ_VERSION); manifest.getMainAttributes().putValue(WEB_CONTEXT_PATH, MOCK_IDE_WEB_CONTEXT_PATH); this.urls = urls; } public boolean isTestMode() { return MOCK_IDE_ARK_BIZ_MAIN_CLASS.equals(className); } public String getClassName() { return className; } public String getMethodName() { return methodName; } @Override public URL[] getUrls() { return this.urls; } @Override public boolean isEntryExist(EntryFilter filter) { return filter.matches(new Entry() { @Override public boolean isDirectory() { return false; } @Override public String getName() { return Constants.ARK_BIZ_MARK_ENTRY; } }); } @Override public URL getUrl() { throw new RuntimeException("unreachable invocation."); } @Override public Manifest getManifest() { return manifest; } @Override public List getNestedArchives(EntryFilter filter) { throw new RuntimeException("unreachable invocation."); } @Override public Archive getNestedArchive(Entry entry) { if (Constants.ARK_BIZ_MARK_ENTRY.equals(entry.getName())) { return new NoopBizArchive(); } throw new RuntimeException("unreachable invocation."); } @Override public InputStream getInputStream(ZipEntry zipEntry) { throw new RuntimeException("unreachable invocation."); } @Override public Iterator iterator() { throw new RuntimeException("unreachable invocation."); } class NoopBizArchive extends JarBizArchive { public NoopBizArchive() { super(null); } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/DirectoryContainerArchive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader; import com.alipay.sofa.ark.spi.archive.Archive; import com.alipay.sofa.ark.spi.archive.ContainerArchive; import java.io.InputStream; import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.jar.Manifest; import java.util.zip.ZipEntry; /** * Ark Container Directory * * @author qilong.zql * @since 0.3.0 */ public class DirectoryContainerArchive implements ContainerArchive { private final URL[] urls; private final static String[] AKR_CONTAINER_JAR = { "aopalliance-1.0", "commons-io-2.7", "guava-33.0.0-jre", "guice-6.0.0", "failureaccess-1.0.1", "javax.inject-1", "logback-core-1.2.9", "logback-classic-1.2.9", "slf4j-api-1.7.32", "log-sofa-boot-starter", "log-sofa-boot", "sofa-common-tools", "netty-all-4.1.109.Final", "netty-transport-4.1.109.Final", "netty-common-4.1.109.Final", "netty-handler-4.1.109.Final", "netty-codec-4.1.109.Final", "netty-buffer-4.1.109.Final", "sofa-ark-parent/core-impl/container/target/classes", "sofa-ark-parent/core-impl/archive/target/classes", "sofa-ark-parent/core/spi/target/classes", "sofa-ark-parent/core/common/target/classes", "sofa-ark-parent/core/exception/target/classes", "sofa-ark-parent/core/api/target/classes" }; public DirectoryContainerArchive(URL[] urls) { this.urls = urls; } public static Set getArkContainerJarMarkers() { return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(AKR_CONTAINER_JAR))); } @Override public URL[] getUrls() { return urls; } @Override public URL getUrl() { throw new RuntimeException("unreachable invocation."); } @Override public Manifest getManifest() { throw new RuntimeException("unreachable invocation."); } @Override public List getNestedArchives(EntryFilter filter) { throw new RuntimeException("unreachable invocation."); } @Override public Archive getNestedArchive(Entry entry) { throw new RuntimeException("unreachable invocation."); } @Override public InputStream getInputStream(ZipEntry zipEntry) { throw new RuntimeException("unreachable invocation."); } @Override public Iterator iterator() { throw new RuntimeException("unreachable invocation."); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/EmbedClassPathArchive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader; import com.alipay.sofa.ark.bootstrap.ClasspathLauncher; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.loader.archive.JarFileArchive; import com.alipay.sofa.ark.spi.archive.Archive; import com.alipay.sofa.ark.spi.archive.BizArchive; import com.alipay.sofa.ark.spi.archive.ContainerArchive; import com.alipay.sofa.ark.spi.archive.PluginArchive; import com.alipay.sofa.ark.spi.constant.Constants; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; /** * A embed classpath archive base on an application fat jar * * @author bingjie.lbj */ public class EmbedClassPathArchive extends ClasspathLauncher.ClassPathArchive { public EmbedClassPathArchive(String className, String method, URL[] urls) throws IOException { super(className, method, urls); } @Override public ContainerArchive getContainerArchive() throws Exception { List urlList = filterUrls(Constants.ARK_CONTAINER_MARK_ENTRY); if (urlList.isEmpty()) { return createDirectoryContainerArchive(); } if (urlList.size() > 1) { throw new ArkRuntimeException("Duplicate Container Jar File Found."); } return new JarContainerArchive(getUrlJarFileArchive(urlList.get(0))); } @Override public List getBizArchives() throws Exception { //将classpath中的biz包载入 List urlList = filterBizUrl(Constants.ARK_BIZ_MARK_ENTRY); List bizArchives = new LinkedList<>(); for (URL url : urlList) { //判断是classpath下的还是fatjar内的biz包 if (url.getPath().contains(".jar!")) { Archive archiveFromJarEntry = getArchiveFromJarEntry(url); if (archiveFromJarEntry != null) { bizArchives.add(new JarBizArchive(archiveFromJarEntry)); } } else { bizArchives .add(new JarBizArchive(new JarFileArchive(FileUtils.file(url.getFile())))); } } return bizArchives; } /** * 从biz包内解析出archive * @param jarUrl biz包的url路径 * @return 依赖的arkbiz包 * @throws IOException */ private Archive getArchiveFromJarEntry(URL jarUrl) throws IOException { String jarPath = jarUrl.getPath().substring(0, jarUrl.getPath().indexOf("!")); String bizPath = jarUrl.getPath().substring(jarUrl.getPath().indexOf("!") + 2); List nestedArchives = new JarFileArchive(FileUtils.file(jarPath)) .getNestedArchives(entry -> entry.getName().equals(bizPath)); if (nestedArchives.isEmpty()) { return null; } return nestedArchives.get(0); } /** * 过滤出biz包 */ public List filterBizUrl(String resource) throws Exception { List urlList = new ArrayList<>(); Enumeration enumeration = super.urlClassLoader.findResources(resource); while (enumeration.hasMoreElements()) { URL resourceUrl = enumeration.nextElement(); String resourceFile = resourceUrl.getFile(); String jarFile = resourceFile.substring(0, resourceFile.length() - resource.length() - FILE_IN_JAR.length()); urlList.add(new URL(jarFile)); } return urlList; } @Override public List getPluginArchives() throws Exception { List urlList = filterUrls(Constants.ARK_PLUGIN_MARK_ENTRY); List pluginArchives = new ArrayList<>(); for (URL url : urlList) { pluginArchives.add(new JarPluginArchive(getUrlJarFileArchive(url))); } return pluginArchives; } protected JarFileArchive getUrlJarFileArchive(URL url) throws IOException { String file = url.getFile(); if (file.contains(FILE_IN_JAR)) { int pos = file.indexOf(FILE_IN_JAR); File fatJarFile = FileUtils.file(file.substring(0, pos)); String nestedJar = file.substring(file.lastIndexOf("/") + 1); JarFileArchive fatJarFileArchive = new JarFileArchive(fatJarFile); List matched = fatJarFileArchive.getNestedArchives(entry -> entry.getName().contains(nestedJar)); return (JarFileArchive) matched.get(0); } else { return new JarFileArchive(FileUtils.file(file)); } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/ExecutableArkBizJar.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader; import com.alipay.sofa.ark.spi.archive.*; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import static com.alipay.sofa.ark.spi.constant.Constants.CONF_BASE_DIR; /** * Executable Ark Biz Fat Jar * * @author qilong.zql * @since 0.1.0 */ public class ExecutableArkBizJar implements ExecutableArchive { public final String SOFA_ARK_CONTAINER = "SOFA-ARK/container/"; public final String SOFA_ARK_MODULE = "SOFA-ARK/biz/"; public final String SOFA_ARK_PLUGIN = "SOFA-ARK/plugin/"; public final Archive archive; public final URL url; public ExecutableArkBizJar(Archive archive) { this(archive, null); } public ExecutableArkBizJar(Archive archive, URL url) { this.archive = archive; this.url = url; } @Override public URL getUrl() throws MalformedURLException { return this.url == null ? this.archive.getUrl() : this.url; } @Override public Manifest getManifest() throws IOException { return archive.getManifest(); } @Override public List getNestedArchives(EntryFilter filter) throws IOException { List nestedArchives = new ArrayList<>(); for (Entry entry : this) { if (filter.matches(entry)) { nestedArchives.add(getNestedArchive(entry)); } } return Collections.unmodifiableList(nestedArchives); } @Override public InputStream getInputStream(ZipEntry zipEntry) throws IOException { return this.archive.getInputStream(zipEntry); } @Override public Archive getNestedArchive(Entry entry) throws IOException { return this.archive.getNestedArchive(entry); } @Override public Iterator iterator() { return this.archive.iterator(); } @Override public ContainerArchive getContainerArchive() throws Exception { List archives = getNestedArchives(new EntryFilter() { @Override public boolean matches(Entry entry) { return !entry.getName().equals(SOFA_ARK_CONTAINER) && entry.getName().startsWith(SOFA_ARK_CONTAINER); } }); if (archives.isEmpty()) { throw new RuntimeException("No ark container archive found!"); } return new JarContainerArchive(archives.get(0)); } /** * Returns the ark-biz module archives that will run upon ark container * @return biz-app archives * @throws Exception */ @Override public List getBizArchives() throws Exception { List archives = getNestedArchives(new EntryFilter() { @Override public boolean matches(Entry entry) { return !entry.getName().equals(SOFA_ARK_MODULE) && entry.getName().startsWith(SOFA_ARK_MODULE); } }); List bizArchives = new ArrayList<>(); for (Archive archive : archives) { bizArchives.add(new JarBizArchive(archive)); } return bizArchives; } /** * Returns the ark plugin archives that will be applied to class isolation strategy of ark container * @return ark plugin archives * @throws Exception */ @Override public List getPluginArchives() throws Exception { List archives = this.archive.getNestedArchives(new EntryFilter() { @Override public boolean matches(Entry entry) { return !entry.getName().equals(SOFA_ARK_PLUGIN) && entry.getName().startsWith(SOFA_ARK_PLUGIN); } }); List pluginArchives = new ArrayList<>(); for (Archive archive : archives) { pluginArchives.add(new JarPluginArchive(archive)); } return pluginArchives; } @Override public List getConfClasspath() throws Exception { List archives = getNestedArchives(new EntryFilter() { @Override public boolean matches(Entry entry) { return entry.getName().startsWith(CONF_BASE_DIR) && entry.isDirectory(); } }); List urls = new ArrayList<>(); for (Archive archive : archives) { urls.add(archive.getUrl()); } return urls; } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/ExplodedBizArchive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader; import com.alipay.sofa.ark.spi.archive.Archive; import com.alipay.sofa.ark.spi.archive.BizArchive; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.jar.Manifest; import java.util.zip.ZipEntry; /** * Ark Biz Module exploded directory archive * * @author bingjie.lbj */ public class ExplodedBizArchive implements BizArchive { private static final String SOFA_ARK_BIZ_LIB = "lib/"; private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; private File file; private URL[] urls; private Manifest manifest; public ExplodedBizArchive(File root) throws IOException { this.file = root; this.urls = scanUrl(); try (FileInputStream fileInputStream = new FileInputStream(new File(root, MANIFEST_NAME));) { this.manifest = new Manifest(fileInputStream); } } private URL[] scanUrl() throws MalformedURLException { List urls = new ArrayList<>(); urls.add(this.file.toURI().toURL()); File libs = new File(file, SOFA_ARK_BIZ_LIB); urls.add(libs.toURI().toURL()); File[] files = libs.listFiles(); if (files != null) { for (File lib : files) { urls.add(lib.toURI().toURL()); } } return urls.toArray(new URL[] {}); } @Override public URL[] getUrls() throws IOException { return urls; } @Override public boolean isEntryExist(EntryFilter filter) { throw new UnsupportedOperationException("unreachable invocation."); } @Override public URL getUrl() throws MalformedURLException { return file.toURI().toURL(); } @Override public Manifest getManifest() throws IOException { return this.manifest; } @Override public List getNestedArchives(EntryFilter filter) throws IOException { throw new UnsupportedOperationException("unreachable invocation."); } @Override public Archive getNestedArchive(Entry entry) throws IOException { throw new UnsupportedOperationException("unreachable invocation."); } @Override public InputStream getInputStream(ZipEntry zipEntry) throws IOException { throw new UnsupportedOperationException("unreachable invocation."); } @Override public Iterator iterator() { throw new UnsupportedOperationException("unreachable invocation."); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/JarBizArchive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader; import com.alipay.sofa.ark.spi.archive.AbstractArchive; import com.alipay.sofa.ark.spi.archive.Archive; import com.alipay.sofa.ark.spi.archive.BizArchive; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.jar.Manifest; import java.util.zip.ZipEntry; /** * Ark Biz Module Fat Jar * * @author qilong.zql * @since 0.1.0 */ public class JarBizArchive extends AbstractArchive implements BizArchive { public final Archive archive; private final String SOFA_ARK_BIZ_LIB = "lib/"; private final String SOFA_ARK_BIZ_LIB_EXPORT = "lib/export"; public JarBizArchive(Archive archive) { this.archive = archive; } @Override public URL getUrl() throws MalformedURLException { return this.archive.getUrl(); } @Override public Manifest getManifest() throws IOException { return this.archive.getManifest(); } @Override public List getNestedArchives(EntryFilter filter) throws IOException { List nestedArchives = new ArrayList<>(); for (Entry entry : this) { if (filter.matches(entry)) { nestedArchives.add(getNestedArchive(entry)); } } return Collections.unmodifiableList(nestedArchives); } @Override public InputStream getInputStream(ZipEntry zipEntry) throws IOException { return this.archive.getInputStream(zipEntry); } @Override public Archive getNestedArchive(Entry entry) throws IOException { return archive.getNestedArchive(entry); } @Override public Iterator iterator() { return this.archive.iterator(); } @Override public URL[] getUrls() throws IOException { return getUrls(new EntryFilter() { @Override public boolean matches(Entry entry) { return entry.getName().startsWith(SOFA_ARK_BIZ_LIB); } }); } public URL[] getExportUrls() throws IOException { return getUrls(new EntryFilter() { @Override public boolean matches(Entry entry) { return entry.getName().startsWith(SOFA_ARK_BIZ_LIB_EXPORT) && !entry.getName().equals(SOFA_ARK_BIZ_LIB_EXPORT); } }); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/JarContainerArchive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader; import com.alipay.sofa.ark.spi.archive.AbstractArchive; import com.alipay.sofa.ark.spi.archive.Archive; import com.alipay.sofa.ark.spi.archive.ContainerArchive; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Iterator; import java.util.jar.Manifest; import java.util.zip.ZipEntry; /** * Ark Container Fat Jar * * @author qilong.zql * @since 0.1.0 */ public class JarContainerArchive extends AbstractArchive implements ContainerArchive { private final Archive archive; private final String SOFA_ARK_CONTAINER_LIB = "lib/"; public JarContainerArchive(Archive archive) { this.archive = archive; } @Override public URL[] getUrls() throws IOException { return getUrls(new EntryFilter() { @Override public boolean matches(Entry entry) { return entry.getName().startsWith(SOFA_ARK_CONTAINER_LIB); } }); } @Override public URL getUrl() throws MalformedURLException { return this.archive.getUrl(); } @Override public Manifest getManifest() throws IOException { return this.archive.getManifest(); } @Override public Archive getNestedArchive(Entry entry) throws IOException { return this.archive.getNestedArchive(entry); } @Override public InputStream getInputStream(ZipEntry zipEntry) throws IOException { return this.archive.getInputStream(zipEntry); } @Override public Iterator iterator() { return this.archive.iterator(); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/JarPluginArchive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader; import com.alipay.sofa.ark.spi.archive.AbstractArchive; import com.alipay.sofa.ark.spi.archive.Archive; import com.alipay.sofa.ark.spi.archive.PluginArchive; import java.io.*; import java.net.MalformedURLException; import java.net.URL; import java.util.*; import java.util.jar.Manifest; import java.util.zip.ZipEntry; /** * * @author qilong.zql * @since 0.1.0 */ public class JarPluginArchive extends AbstractArchive implements PluginArchive { public final Archive archive; private URL[] extensionUrls; private final static String SOFA_ARK_PLUGIN_LIB = "lib/"; public JarPluginArchive(Archive archive) { this.archive = archive; } public URL[] getExtensionUrls() { return extensionUrls; } @Override public URL getUrl() throws MalformedURLException { return this.archive.getUrl(); } @Override public Manifest getManifest() throws IOException { return this.archive.getManifest(); } @Override public List getNestedArchives(EntryFilter filter) throws IOException { List nestedArchives = new ArrayList<>(); for (Entry entry : this) { if (filter.matches(entry)) { nestedArchives.add(getNestedArchive(entry)); } } return Collections.unmodifiableList(nestedArchives); } @Override public InputStream getInputStream(ZipEntry zipEntry) throws IOException { return this.archive.getInputStream(zipEntry); } @Override public Archive getNestedArchive(Entry entry) throws IOException { return this.archive.getNestedArchive(entry); } @Override public Iterator iterator() { return this.archive.iterator(); } /** * fetch classpath to startup sofa-ark plugin * * @return */ @Override public URL[] getUrls() throws IOException { return getUrls(new EntryFilter() { @Override public boolean matches(Entry entry) { return entry.getName().startsWith(SOFA_ARK_PLUGIN_LIB) && !SOFA_ARK_PLUGIN_LIB.equals(entry.getName()); } }); } @Override public void setExtensionUrls(URL[] extensionUrls) { this.extensionUrls = extensionUrls; } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/archive/ExplodedArchive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.archive; import com.alipay.sofa.ark.spi.archive.Archive; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Deque; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; import java.util.jar.Manifest; import java.util.zip.ZipEntry; /** * {@link Archive} implementation backed by an exploded archive directory. * * @author Phillip Webb * @author Andy Wilkinson */ public class ExplodedArchive implements Archive { private static final Set SKIPPED_NAMES = new HashSet<>(Arrays.asList(".", "..")); private final File root; private final boolean recursive; private File manifestFile; private Manifest manifest; /** * Create a new {@link ExplodedArchive} instance. * @param root the root folder */ public ExplodedArchive(File root) { this(root, true); } /** * Create a new {@link ExplodedArchive} instance. * @param root the root folder * @param recursive if recursive searching should be used to locate the manifest. * Defaults to {@code true}, folders with a large tree might want to set this to * {@code * false}. */ public ExplodedArchive(File root, boolean recursive) { if (!root.exists() || !root.isDirectory()) { throw new IllegalArgumentException("Invalid source folder " + root); } this.root = root; this.recursive = recursive; this.manifestFile = getManifestFile(root); } private File getManifestFile(File root) { File metaInf = new File(root, "META-INF"); return new File(metaInf, "MANIFEST.MF"); } @Override public URL getUrl() throws MalformedURLException { return this.root.toURI().toURL(); } @Override public Manifest getManifest() throws IOException { if (this.manifest == null && this.manifestFile.exists()) { try (FileInputStream inputStream = new FileInputStream(this.manifestFile)) { this.manifest = new Manifest(inputStream); } } return this.manifest; } @Override public List getNestedArchives(EntryFilter filter) throws IOException { List nestedArchives = new ArrayList<>(); for (Entry entry : this) { if (filter.matches(entry)) { nestedArchives.add(getNestedArchive(entry)); } } return Collections.unmodifiableList(nestedArchives); } @Override public InputStream getInputStream(ZipEntry zipEntry) throws IOException { throw new RuntimeException("unreachable invocation."); } @Override public Iterator iterator() { return new FileEntryIterator(this.root, this.recursive); } public Archive getNestedArchive(Entry entry) throws IOException { File file = ((FileEntry) entry).getFile(); return (file.isDirectory() ? new ExplodedArchive(file) : new JarFileArchive(file)); } @Override public String toString() { try { return getUrl().toString(); } catch (Exception ex) { return "exploded archive"; } } /** * File based {@link Entry} {@link Iterator}. */ private static class FileEntryIterator implements Iterator { private final Comparator entryComparator = new EntryComparator(); private final File root; private final boolean recursive; private final Deque> stack = new LinkedList<>(); private File current; FileEntryIterator(File root, boolean recursive) { this.root = root; this.recursive = recursive; this.stack.add(listFiles(root)); this.current = poll(); } @Override public boolean hasNext() { return this.current != null; } @Override public Entry next() { if (this.current == null) { throw new NoSuchElementException(); } File file = this.current; if (file.isDirectory() && (this.recursive || file.getParentFile().equals(this.root))) { this.stack.addFirst(listFiles(file)); } this.current = poll(); String name = file.toURI().getPath().substring(this.root.toURI().getPath().length()); return new FileEntry(name, file); } private Iterator listFiles(File file) { File[] files = file.listFiles(); if (files == null) { return Collections. emptyList().iterator(); } Arrays.sort(files, this.entryComparator); return Arrays.asList(files).iterator(); } private File poll() { while (!this.stack.isEmpty()) { while (this.stack.peek().hasNext()) { File file = this.stack.peek().next(); if (!SKIPPED_NAMES.contains(file.getName())) { return file; } } this.stack.poll(); } return null; } @Override public void remove() { throw new UnsupportedOperationException("remove"); } /** * {@link Comparator} that orders {@link File} entries by their absolute paths. */ private static class EntryComparator implements Comparator { @Override public int compare(File o1, File o2) { return o1.getAbsolutePath().compareTo(o2.getAbsolutePath()); } } } /** * {@link Entry} backed by a File. */ private static class FileEntry implements Entry { private final String name; private final File file; FileEntry(String name, File file) { this.name = name; this.file = file; } public File getFile() { return this.file; } @Override public boolean isDirectory() { return this.file.isDirectory(); } @Override public String getName() { return this.name; } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/archive/JarFileArchive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.archive; import java.io.*; import java.net.MalformedURLException; import java.net.URL; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.loader.jar.JarFile; import com.alipay.sofa.ark.loader.data.RandomAccessData.ResourceAccess; import com.alipay.sofa.ark.spi.archive.Archive; /** * {@link Archive} implementation backed by a {@link JarFile}. * * @author Phillip Webb * @author Andy Wilkinson */ public class JarFileArchive implements Archive { private static final String UNPACK_MARKER = "UNPACK:"; private static final int BUFFER_SIZE = 32 * 1024; private final JarFile jarFile; private URL url; private File tempUnpackFolder; public JarFileArchive(File file) throws IOException { this(file, null); } public JarFileArchive(File file, URL url) throws IOException { this(new JarFile(file)); this.url = url; } public JarFileArchive(JarFile jarFile) { this.jarFile = jarFile; } @Override public URL getUrl() throws MalformedURLException { if (this.url != null) { return this.url; } return this.jarFile.getUrl(); } @Override public Manifest getManifest() throws IOException { return this.jarFile.getManifest(); } @Override public List getNestedArchives(EntryFilter filter) throws IOException { List nestedArchives = new ArrayList<>(); for (Entry entry : this) { if (filter.matches(entry)) { nestedArchives.add(getNestedArchive(entry)); } } return Collections.unmodifiableList(nestedArchives); } @Override public InputStream getInputStream(ZipEntry zipEntry) throws IOException { return this.jarFile.getInputStream(zipEntry); } @Override public Iterator iterator() { return new EntryIterator(this.jarFile.entries()); } public Properties getPomProperties() throws IOException { return this.jarFile.getPomProperties(); } public Archive getNestedArchive(Entry entry) throws IOException { JarEntry jarEntry = ((JarFileEntry) entry).getJarEntry(); if (jarEntry.getComment() != null && jarEntry.getComment().startsWith(UNPACK_MARKER)) { return getUnpackedNestedArchive(jarEntry); } try { JarFile jarFile = this.jarFile.getNestedJarFile(jarEntry); return new JarFileArchive(jarFile); } catch (Exception ex) { throw new IllegalStateException("Failed to get nested archive for entry " + entry.getName(), ex); } } private Archive getUnpackedNestedArchive(JarEntry jarEntry) throws IOException { String name = jarEntry.getName(); if (name.lastIndexOf("/") != -1) { name = name.substring(name.lastIndexOf("/") + 1); } File file = new File(getTempUnpackFolder(), name); if (!file.exists() || file.length() != jarEntry.getSize()) { unpack(jarEntry, file); } return new JarFileArchive(file, file.toURI().toURL()); } private File getTempUnpackFolder() { if (this.tempUnpackFolder == null) { File tempFolder = FileUtils.file(System.getProperty("java.io.tmpdir")); this.tempUnpackFolder = createUnpackFolder(tempFolder); } return this.tempUnpackFolder; } private File createUnpackFolder(File parent) { int attempts = 0; while (attempts++ < 1000) { String fileName = FileUtils.file(this.jarFile.getName()).getName(); File unpackFolder = new File(parent, fileName + "-spring-boot-libs-" + UUID.randomUUID()); if (unpackFolder.mkdirs()) { return unpackFolder; } } throw new IllegalStateException("Failed to create unpack folder in directory '" + parent + "'"); } private void unpack(JarEntry entry, File file) throws IOException { InputStream inputStream = this.jarFile.getInputStream(entry, ResourceAccess.ONCE); try { OutputStream outputStream = new FileOutputStream(file); try { byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.flush(); } finally { outputStream.close(); } } finally { inputStream.close(); } } @Override public String toString() { try { return getUrl().toString(); } catch (Exception ex) { return "jar archive"; } } /** * {@link Archive.Entry} iterator implementation backed by {@link JarEntry}. */ private static class EntryIterator implements Iterator { private final Enumeration enumeration; EntryIterator(Enumeration enumeration) { this.enumeration = enumeration; } @Override public boolean hasNext() { return this.enumeration.hasMoreElements(); } @Override public Entry next() { return new JarFileEntry(this.enumeration.nextElement()); } @Override public void remove() { throw new UnsupportedOperationException("remove"); } } /** * {@link Archive.Entry} implementation backed by a {@link JarEntry}. */ public static class JarFileEntry implements Entry { private final JarEntry jarEntry; public JarFileEntry(JarEntry jarEntry) { this.jarEntry = jarEntry; } public JarEntry getJarEntry() { return this.jarEntry; } @Override public boolean isDirectory() { return this.jarEntry.isDirectory(); } @Override public String getName() { return this.jarEntry.getName(); } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/data/RandomAccessData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.data; import java.io.IOException; import java.io.InputStream; /** * Interface that provides read-only random access to some underlying data. * Implementations must allow concurrent reads in a thread-safe manner. * * @author Phillip Webb */ public interface RandomAccessData { /** * Returns an {@link InputStream} that can be used to read the underlying data. The * caller is responsible close the underlying stream. * @param access hint indicating how the underlying data should be accessed * @return a new input stream that can be used to read the underlying data. * @throws IOException if the stream cannot be opened */ InputStream getInputStream(ResourceAccess access) throws IOException; /** * Returns a new {@link RandomAccessData} for a specific subsection of this data. * @param offset the offset of the subsection * @param length the length of the subsection * @return the subsection data */ RandomAccessData getSubsection(long offset, long length); /** * Returns the size of the data. * @return the size */ long getSize(); /** * Lock modes for accessing the underlying resource. */ enum ResourceAccess { /** * Obtain access to the underlying resource once and keep it until the stream is * closed. */ ONCE, /** * Obtain access to the underlying resource on each read, releasing it when done. */ PER_READ } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/data/RandomAccessDataFile.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.data; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Semaphore; /** * {@link RandomAccessData} implementation backed by a {@link RandomAccessFile}. * * @author Phillip Webb */ public class RandomAccessDataFile implements RandomAccessData { private static final int DEFAULT_CONCURRENT_READS = 4; private final File file; private final FilePool filePool; private final long offset; private final long length; /** * Create a new {@link RandomAccessDataFile} backed by the specified file. * @param file the underlying file * @throws IllegalArgumentException if the file is null or does not exist * @see #RandomAccessDataFile(File, int) */ public RandomAccessDataFile(File file) { this(file, DEFAULT_CONCURRENT_READS); } /** * Create a new {@link RandomAccessDataFile} backed by the specified file. * @param file the underlying file * @param concurrentReads the maximum number of concurrent reads allowed on the * underlying file before blocking * @throws IllegalArgumentException if the file is null or does not exist * @see #RandomAccessDataFile(File) */ public RandomAccessDataFile(File file, int concurrentReads) { if (file == null) { throw new IllegalArgumentException("File must not be null"); } if (!file.exists()) { throw new IllegalArgumentException(String.format("File must exist: %s", file.getPath())); } this.file = file; this.filePool = new FilePool(file, concurrentReads); this.offset = 0L; this.length = file.length(); } /** * Private constructor used to create a {@link #getSubsection(long, long) subsection}. * @param file the underlying file * @param pool the underlying pool * @param offset the offset of the section * @param length the length of the section */ private RandomAccessDataFile(File file, FilePool pool, long offset, long length) { this.file = file; this.filePool = pool; this.offset = offset; this.length = length; } /** * Returns the underlying File. * @return the underlying file */ public File getFile() { return this.file; } @Override public InputStream getInputStream(ResourceAccess access) throws IOException { return new DataInputStream(access); } @Override public RandomAccessData getSubsection(long offset, long length) { if (offset < 0 || length < 0 || offset + length > this.length) { throw new IndexOutOfBoundsException(); } return new RandomAccessDataFile(this.file, this.filePool, this.offset + offset, length); } @Override public long getSize() { return this.length; } public void close() throws IOException { this.filePool.close(); } /** * {@link RandomAccessDataFile}. */ private class DataInputStream extends InputStream { private RandomAccessFile file; private int position; DataInputStream(ResourceAccess access) throws IOException { if (access == ResourceAccess.ONCE) { this.file = new RandomAccessFile(RandomAccessDataFile.this.file, "r"); this.file.seek(RandomAccessDataFile.this.offset); } } @Override public int read() throws IOException { return doRead(null, 0, 1); } @Override public int read(byte[] b) throws IOException { return read(b, 0, b == null ? 0 : b.length); } @Override public int read(byte[] b, int off, int len) throws IOException { if (b == null) { throw new NullPointerException("Bytes must not be null"); } return doRead(b, off, len); } /** * Perform the actual read. * @param b the bytes to read or {@code null} when reading a single byte * @param off the offset of the byte array * @param len the length of data to read * @return the number of bytes read into {@code b} or the actual read byte if * {@code b} is {@code null}. Returns -1 when the end of the stream is reached * @throws IOException in case of I/O errors */ public int doRead(byte[] b, int off, int len) throws IOException { if (len == 0) { return 0; } int cappedLen = cap(len); if (cappedLen <= 0) { return -1; } RandomAccessFile file = this.file; try { if (file == null) { file = RandomAccessDataFile.this.filePool.acquire(); file.seek(RandomAccessDataFile.this.offset + this.position); } if (b == null) { int rtn = file.read(); moveOn(rtn == -1 ? 0 : 1); return rtn; } else { return (int) moveOn(file.read(b, off, cappedLen)); } } finally { if (this.file == null && file != null) { RandomAccessDataFile.this.filePool.release(file); } } } @Override public long skip(long n) { return (n <= 0 ? 0 : moveOn(cap(n))); } @Override public void close() throws IOException { if (this.file != null) { this.file.close(); } } /** * Cap the specified value such that it cannot exceed the number of bytes * remaining. * @param n the value to cap * @return the capped value */ private int cap(long n) { return (int) Math.min(RandomAccessDataFile.this.length - this.position, n); } /** * Move the stream position forwards the specified amount. * @param amount the amount to move * @return the amount moved */ private long moveOn(int amount) { this.position += amount; return amount; } } /** * Manage a pool that can be used to perform concurrent reads on the underlying * {@link RandomAccessFile}. */ static class FilePool { private final File file; private final int size; private final Semaphore available; private final Queue files; FilePool(File file, int size) { this.file = file; this.size = size; this.available = new Semaphore(size); this.files = new ConcurrentLinkedQueue<>(); } public RandomAccessFile acquire() throws IOException { this.available.acquireUninterruptibly(); RandomAccessFile file = this.files.poll(); if (file != null) { return file; } return new RandomAccessFile(this.file, "r"); } public void release(RandomAccessFile file) { this.files.add(file); this.available.release(); } public void close() throws IOException { this.available.acquireUninterruptibly(this.size); try { RandomAccessFile pooledFile = this.files.poll(); while (pooledFile != null) { pooledFile.close(); pooledFile = this.files.poll(); } } finally { this.available.release(this.size); } } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/jar/AsciiBytes.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import java.nio.charset.Charset; /** * Simple wrapper around a byte array that represents an ASCII. Used for performance * reasons to save constructing Strings for ZIP data. * * @author Phillip Webb * @author Andy Wilkinson */ final public class AsciiBytes { private static final Charset UTF_8 = Charset.forName("UTF-8"); private final byte[] bytes; private final int offset; private final int length; private String string; private int hash; /** * Create a new {@link AsciiBytes} from the specified String. * @param string the source string */ public AsciiBytes(String string) { this(string.getBytes(UTF_8)); this.string = string; } /** * Create a new {@link AsciiBytes} from the specified bytes. NOTE: underlying bytes * are not expected to change. * @param bytes the source bytes */ public AsciiBytes(byte[] bytes) { this(bytes, 0, bytes.length); } /** * Create a new {@link AsciiBytes} from the specified bytes. NOTE: underlying bytes * are not expected to change. * @param bytes the source bytes * @param offset the offset * @param length the length */ public AsciiBytes(byte[] bytes, int offset, int length) { if (offset < 0 || length < 0 || (offset + length) > bytes.length) { throw new IndexOutOfBoundsException(); } this.bytes = bytes; this.offset = offset; this.length = length; } public int length() { return this.length; } public boolean startsWith(AsciiBytes prefix) { if (this == prefix) { return true; } if (prefix.length > this.length) { return false; } for (int i = 0; i < prefix.length; i++) { if (this.bytes[i + this.offset] != prefix.bytes[i + prefix.offset]) { return false; } } return true; } public boolean endsWith(AsciiBytes postfix) { if (this == postfix) { return true; } if (postfix.length > this.length) { return false; } for (int i = 0; i < postfix.length; i++) { if (this.bytes[this.offset + (this.length - 1) - i] != postfix.bytes[postfix.offset + (postfix.length - 1) - i]) { return false; } } return true; } public AsciiBytes substring(int beginIndex) { return substring(beginIndex, this.length); } public AsciiBytes substring(int beginIndex, int endIndex) { int length = endIndex - beginIndex; if (this.offset + length > this.bytes.length) { throw new IndexOutOfBoundsException(); } return new AsciiBytes(this.bytes, this.offset + beginIndex, length); } public AsciiBytes append(String string) { if (string == null || string.isEmpty()) { return this; } return append(string.getBytes(UTF_8)); } public AsciiBytes append(AsciiBytes asciiBytes) { if (asciiBytes == null || asciiBytes.length() == 0) { return this; } return append(asciiBytes.bytes); } public AsciiBytes append(byte[] bytes) { if (bytes == null || bytes.length == 0) { return this; } byte[] combined = new byte[this.length + bytes.length]; System.arraycopy(this.bytes, this.offset, combined, 0, this.length); System.arraycopy(bytes, 0, combined, this.length, bytes.length); return new AsciiBytes(combined); } @Override public String toString() { if (this.string == null) { this.string = new String(this.bytes, this.offset, this.length, UTF_8); } return this.string; } @Override public int hashCode() { int hash = this.hash; if (hash == 0 && this.bytes.length > 0) { for (int i = this.offset; i < this.offset + this.length; i++) { int b = this.bytes[i]; if (b < 0) { b = b & 0x7F; int limit; int excess = 0x80; if (b < 96) { limit = 1; excess += 0x40 << 6; } else if (b < 112) { limit = 2; excess += (0x60 << 12) + (0x80 << 6); } else { limit = 3; excess += (0x70 << 18) + (0x80 << 12) + (0x80 << 6); } for (int j = 0; j < limit; j++) { b = (b << 6) + (this.bytes[++i] & 0xFF); } b -= excess; } if (b <= 0xFFFF) { hash = 31 * hash + b; } else { hash = 31 * hash + ((b >> 0xA) + 0xD7C0); hash = 31 * hash + ((b & 0x3FF) + 0xDC00); } } this.hash = hash; } return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (obj.getClass().equals(AsciiBytes.class)) { AsciiBytes other = (AsciiBytes) obj; if (this.length == other.length) { for (int i = 0; i < this.length; i++) { if (this.bytes[this.offset + i] != other.bytes[other.offset + i]) { return false; } } return true; } } return false; } static String toString(byte[] bytes) { return new String(bytes, UTF_8); } public static int hashCode(String string) { // We're compatible with String's hashCode(). return string.hashCode(); } public static int hashCode(int hash, String string) { for (int i = 0; i < string.length(); i++) { hash = 31 * hash + string.charAt(i); } return hash; } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/jar/Bytes.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import com.alipay.sofa.ark.loader.data.RandomAccessData; import com.alipay.sofa.ark.loader.data.RandomAccessData.ResourceAccess; import java.io.IOException; import java.io.InputStream; /** * Utilities for dealing with bytes from ZIP files. * * @author Phillip Webb */ final public class Bytes { private static final byte[] EMPTY_BYTES = new byte[] {}; private Bytes() { } public static byte[] get(RandomAccessData data) throws IOException { InputStream inputStream = data.getInputStream(ResourceAccess.ONCE); try { return get(inputStream, data.getSize()); } finally { inputStream.close(); } } public static byte[] get(InputStream inputStream, long length) throws IOException { if (length == 0) { return EMPTY_BYTES; } byte[] bytes = new byte[(int) length]; if (!fill(inputStream, bytes)) { throw new IOException("Unable to read bytes"); } return bytes; } public static boolean fill(InputStream inputStream, byte[] bytes) throws IOException { return fill(inputStream, bytes, 0, bytes.length); } private static boolean fill(InputStream inputStream, byte[] bytes, int offset, int length) throws IOException { while (length > 0) { int read = inputStream.read(bytes, offset, length); if (read == -1) { return false; } offset += read; length = -read; } return true; } public static long littleEndianValue(byte[] bytes, int offset, int length) { long value = 0; for (int i = length - 1; i >= 0; i--) { value = ((value << 8) | (bytes[offset + i] & 0xFF)); } return value; } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/jar/CentralDirectoryEndRecord.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import com.alipay.sofa.ark.loader.data.RandomAccessData; import java.io.IOException; /** * A ZIP File "End of central directory record" (EOCD). * * @author Phillip Webb * @author Andy Wilkinson * @see Zip File Format */ final public class CentralDirectoryEndRecord { private static final int MINIMUM_SIZE = 22; private static final int MAXIMUM_COMMENT_LENGTH = 0xFFFF; private static final int MAXIMUM_SIZE = MINIMUM_SIZE + MAXIMUM_COMMENT_LENGTH; private static final int SIGNATURE = 0x06054b50; private static final int COMMENT_LENGTH_OFFSET = 20; private static final int READ_BLOCK_SIZE = 256; private byte[] block; private int offset; private int size; /** * Create a new {@link CentralDirectoryEndRecord} instance from the specified * {@link RandomAccessData}, searching backwards from the end until a valid block is * located. * @param data the source data * @throws IOException in case of I/O errors */ public CentralDirectoryEndRecord(RandomAccessData data) throws IOException { this.block = createBlockFromEndOfData(data, READ_BLOCK_SIZE); this.size = MINIMUM_SIZE; this.offset = this.block.length - this.size; while (!isValid()) { this.size++; if (this.size > this.block.length) { if (this.size >= MAXIMUM_SIZE || this.size > data.getSize()) { throw new IOException("Unable to find ZIP central directory " + "records after reading " + this.size + " bytes"); } this.block = createBlockFromEndOfData(data, this.size + READ_BLOCK_SIZE); } this.offset = this.block.length - this.size; } } private byte[] createBlockFromEndOfData(RandomAccessData data, int size) throws IOException { int length = (int) Math.min(data.getSize(), size); return Bytes.get(data.getSubsection(data.getSize() - length, length)); } public boolean isValid() { if (this.block.length < MINIMUM_SIZE || Bytes.littleEndianValue(this.block, this.offset, 4) != SIGNATURE) { return false; } // Total size must be the structure size + comment long commentLength = Bytes.littleEndianValue(this.block, this.offset + COMMENT_LENGTH_OFFSET, 2); return this.size == MINIMUM_SIZE + commentLength; } /** * Returns the location in the data that the archive actually starts. For most files * the archive data will start at 0, however, it is possible to have prefixed bytes * (often used for startup scripts) at the beginning of the data. * @param data the source data * @return the offset within the data where the archive begins */ public long getStartOfArchive(RandomAccessData data) { long length = Bytes.littleEndianValue(this.block, this.offset + 12, 4); long specifiedOffset = Bytes.littleEndianValue(this.block, this.offset + 16, 4); long actualOffset = data.getSize() - this.size - length; return actualOffset - specifiedOffset; } /** * Return the bytes of the "Central directory" based on the offset indicated in this * record. * @param data the source data * @return the central directory data */ public RandomAccessData getCentralDirectory(RandomAccessData data) { long offset = Bytes.littleEndianValue(this.block, this.offset + 16, 4); long length = Bytes.littleEndianValue(this.block, this.offset + 12, 4); return data.getSubsection(offset, length); } /** * Return the number of ZIP entries in the file. * @return the number of records in the zip */ public int getNumberOfRecords() { long numberOfRecords = Bytes.littleEndianValue(this.block, this.offset + 10, 2); if (numberOfRecords == 0xFFFF) { throw new IllegalStateException("Zip64 archives are not supported"); } return (int) numberOfRecords; } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/jar/CentralDirectoryFileHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import com.alipay.sofa.ark.loader.data.RandomAccessData; import java.io.IOException; import java.util.Calendar; import java.util.GregorianCalendar; /** * A ZIP File "Central directory file header record" (CDFH). * * @author Phillip Webb * @author Andy Wilkinson * @see Zip File Format */ final public class CentralDirectoryFileHeader implements FileHeader { private static final AsciiBytes SLASH = new AsciiBytes("/"); private static final byte[] NO_EXTRA = {}; private static final AsciiBytes NO_COMMENT = new AsciiBytes(""); private byte[] header; private int headerOffset; private AsciiBytes name; private byte[] extra; private AsciiBytes comment; private long localHeaderOffset; public CentralDirectoryFileHeader() { } public CentralDirectoryFileHeader(byte[] header, int headerOffset, AsciiBytes name, byte[] extra, AsciiBytes comment, long localHeaderOffset) { super(); this.header = header; this.headerOffset = headerOffset; this.name = name; this.extra = extra; this.comment = comment; this.localHeaderOffset = localHeaderOffset; } public void load(byte[] data, int dataOffset, RandomAccessData variableData, int variableOffset, JarEntryFilter filter) throws IOException { // Load fixed part this.header = data; this.headerOffset = dataOffset; long nameLength = Bytes.littleEndianValue(data, dataOffset + 28, 2); long extraLength = Bytes.littleEndianValue(data, dataOffset + 30, 2); long commentLength = Bytes.littleEndianValue(data, dataOffset + 32, 2); this.localHeaderOffset = Bytes.littleEndianValue(data, dataOffset + 42, 4); // Load variable part dataOffset += 46; if (variableData != null) { data = Bytes.get(variableData.getSubsection(variableOffset + 46, nameLength + extraLength + commentLength)); dataOffset = 0; } this.name = new AsciiBytes(data, dataOffset, (int) nameLength); if (filter != null) { this.name = filter.apply(this.name); } this.extra = NO_EXTRA; this.comment = NO_COMMENT; if (extraLength > 0) { this.extra = new byte[(int) extraLength]; System.arraycopy(data, (int) (dataOffset + nameLength), this.extra, 0, this.extra.length); } if (commentLength > 0) { this.comment = new AsciiBytes(data, (int) (dataOffset + nameLength + extraLength), (int) commentLength); } } public AsciiBytes getName() { return this.name; } @Override public boolean hasName(String name, String suffix) { return this.name.equals(new AsciiBytes(suffix == null ? name : name + suffix)); } public boolean isDirectory() { return this.name.endsWith(SLASH); } @Override public int getMethod() { return (int) Bytes.littleEndianValue(this.header, this.headerOffset + 10, 2); } public long getTime() { long date = Bytes.littleEndianValue(this.header, this.headerOffset + 14, 2); long time = Bytes.littleEndianValue(this.header, this.headerOffset + 12, 2); return decodeMsDosFormatDateTime(date, time).getTimeInMillis(); } /** * Decode MS-DOS Date Time details. See * mindprod.com/jgloss/zip.html for * more details of the format. * @param date the date part * @param time the time part * @return a {@link Calendar} containing the decoded date. */ private Calendar decodeMsDosFormatDateTime(long date, long time) { int year = (int) ((date >> 9) & 0x7F) + 1980; int month = (int) ((date >> 5) & 0xF) - 1; int day = (int) (date & 0x1F); int hours = (int) ((time >> 11) & 0x1F); int minutes = (int) ((time >> 5) & 0x3F); int seconds = (int) ((time << 1) & 0x3E); return new GregorianCalendar(year, month, day, hours, minutes, seconds); } public long getCrc() { return Bytes.littleEndianValue(this.header, this.headerOffset + 16, 4); } @Override public long getCompressedSize() { return Bytes.littleEndianValue(this.header, this.headerOffset + 20, 4); } @Override public long getSize() { return Bytes.littleEndianValue(this.header, this.headerOffset + 24, 4); } public byte[] getExtra() { return this.extra; } public AsciiBytes getComment() { return this.comment; } @Override public long getLocalHeaderOffset() { return this.localHeaderOffset; } @Override public CentralDirectoryFileHeader clone() { byte[] header = new byte[46]; System.arraycopy(this.header, this.headerOffset, header, 0, header.length); return new CentralDirectoryFileHeader(header, 0, this.name, header, this.comment, this.localHeaderOffset); } public static CentralDirectoryFileHeader fromRandomAccessData(RandomAccessData data, int offset, JarEntryFilter filter) throws IOException { CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader(); byte[] bytes = Bytes.get(data.getSubsection(offset, 46)); fileHeader.load(bytes, 0, data, offset, filter); return fileHeader; } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/jar/CentralDirectoryParser.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import com.alipay.sofa.ark.loader.data.RandomAccessData; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Parses the central directory from a JAR file. * * @author Phillip Webb * @see CentralDirectoryVisitor */ final public class CentralDirectoryParser { private final static int CENTRAL_DIRECTORY_HEADER_BASE_SIZE = 46; private final List visitors = new ArrayList<>(); public T addVisitor(T visitor) { this.visitors.add(visitor); return visitor; } /** * Parse the source data, triggering {@link CentralDirectoryVisitor visitors}. * @param data the source data * @param skipPrefixBytes if prefix bytes should be skipped * @return The actual archive data without any prefix bytes * @throws IOException on error */ public RandomAccessData parse(RandomAccessData data, boolean skipPrefixBytes) throws IOException { CentralDirectoryEndRecord endRecord = new CentralDirectoryEndRecord(data); if (skipPrefixBytes) { data = getArchiveData(endRecord, data); } RandomAccessData centralDirectoryData = endRecord.getCentralDirectory(data); visitStart(endRecord, centralDirectoryData); parseEntries(endRecord, centralDirectoryData); visitEnd(); return data; } private void parseEntries(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) throws IOException { byte[] bytes = Bytes.get(centralDirectoryData); CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader(); int dataOffset = 0; for (int i = 0; i < endRecord.getNumberOfRecords(); i++) { fileHeader.load(bytes, dataOffset, null, 0, null); visitFileHeader(dataOffset, fileHeader); dataOffset += CENTRAL_DIRECTORY_HEADER_BASE_SIZE + fileHeader.getName().length() + fileHeader.getComment().length() + fileHeader.getExtra().length; } } private RandomAccessData getArchiveData(CentralDirectoryEndRecord endRecord, RandomAccessData data) { long offset = endRecord.getStartOfArchive(data); if (offset == 0) { return data; } return data.getSubsection(offset, data.getSize() - offset); } private void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) { for (CentralDirectoryVisitor visitor : this.visitors) { visitor.visitStart(endRecord, centralDirectoryData); } } private void visitFileHeader(int dataOffset, CentralDirectoryFileHeader fileHeader) { for (CentralDirectoryVisitor visitor : this.visitors) { visitor.visitFileHeader(fileHeader, dataOffset); } } private void visitEnd() { for (CentralDirectoryVisitor visitor : this.visitors) { visitor.visitEnd(); } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/jar/CentralDirectoryVisitor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import com.alipay.sofa.ark.loader.data.RandomAccessData; /** * Callback visitor triggered by {@link CentralDirectoryParser}. * * @author Phillip Webb */ public interface CentralDirectoryVisitor { void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData); void visitFileHeader(CentralDirectoryFileHeader fileHeader, int dataOffset); void visitEnd(); } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/jar/FileHeader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import java.util.zip.ZipEntry; /** * A file header record that has been loaded from a Jar file. * * @author Phillip Webb * @see JarEntry * @see CentralDirectoryFileHeader */ interface FileHeader { /** * Returns {@code true} if the header has the given name. * @param name the name to test * @param suffix an additional suffix (or {@code null}) * @return {@code true} if the header has the given name */ boolean hasName(String name, String suffix); /** * Return the offset of the load file header within the archive data. * @return the local header offset */ long getLocalHeaderOffset(); /** * Return the compressed size of the entry. * @return the compressed size. */ long getCompressedSize(); /** * Return the uncompressed size of the entry. * @return the uncompressed size. */ long getSize(); /** * Return the method used to compress the data. * @return the zip compression method * @see ZipEntry#STORED * @see ZipEntry#DEFLATED */ int getMethod(); } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/jar/Handler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.common.util.StringUtils; import java.io.File; import java.io.IOException; import java.lang.ref.SoftReference; import java.lang.reflect.Method; import java.net.*; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; /** * {@link URLStreamHandler} for Spring Boot loader {@link JarFile}s. * * @author Phillip Webb * @author Andy Wilkinson * @see JarFile#registerUrlProtocolHandler() */ public class Handler extends URLStreamHandler { // NOTE: in order to be found as a URL protocol handler, this class must be public, // must be named Handler and must be in a package ending '.jar' private static final String JAR_PROTOCOL = "jar:"; private static final String FILE_PROTOCOL = "file:"; private static final String SEPARATOR = "!/"; private static final String[] FALLBACK_HANDLERS = { "sun.net.www.protocol.jar.Handler" }; private static final Method OPEN_CONNECTION_METHOD; static { Method method = null; try { method = URLStreamHandler.class.getDeclaredMethod("openConnection", URL.class); } catch (Exception ex) { // Swallow and ignore } OPEN_CONNECTION_METHOD = method; } private static SoftReference> rootFileCache; static { rootFileCache = new SoftReference<>(null); } private final JarFile jarFile; private URLStreamHandler fallbackHandler; public Handler() { this(null); } public Handler(JarFile jarFile) { this.jarFile = jarFile; } @Override protected URLConnection openConnection(URL url) throws IOException { if (this.jarFile != null) { return JarURLConnection.get(url, this.jarFile); } try { return JarURLConnection.get(url, getRootJarFileFromUrl(url)); } catch (Exception ex) { return openFallbackConnection(url, ex); } } private URLConnection openFallbackConnection(URL url, Exception reason) throws IOException { try { return openConnection(getFallbackHandler(), url); } catch (Exception ex) { if (reason instanceof IOException) { log(false, ex); throw (IOException) reason; } log(true, ex); if (reason instanceof RuntimeException) { throw (RuntimeException) reason; } throw new IllegalStateException(reason); } } private void log(boolean warning, Exception cause) { try { Logger.getLogger(getClass().getName()).log((warning ? Level.WARNING : Level.FINEST), "Unable to open fallback handler", cause); } catch (Exception ex) { if (warning) { System.err.println("WARNING: " + "Unable to open fallback handler"); //NOPMD } } } private URLStreamHandler getFallbackHandler() { if (this.fallbackHandler != null) { return this.fallbackHandler; } for (String handlerClassName : FALLBACK_HANDLERS) { try { Class handlerClass = Class.forName(handlerClassName); this.fallbackHandler = (URLStreamHandler) handlerClass.newInstance(); return this.fallbackHandler; } catch (Exception ex) { // Ignore } } throw new IllegalStateException("Unable to find fallback handler"); } private URLConnection openConnection(URLStreamHandler handler, URL url) throws Exception { if (OPEN_CONNECTION_METHOD == null) { throw new IllegalStateException("Unable to invoke fallback open connection method"); } OPEN_CONNECTION_METHOD.setAccessible(true); return (URLConnection) OPEN_CONNECTION_METHOD.invoke(handler, url); } @Override protected void parseURL(URL context, String spec, int start, int limit) { if (StringUtils.startWithToLowerCase(spec, JAR_PROTOCOL)) { setFile(context, getFileFromSpec(spec.substring(start, limit))); } else { setFile(context, getFileFromContext(context, spec.substring(start, limit))); } } private String getFileFromSpec(String spec) { int separatorIndex = spec.lastIndexOf("!/"); if (separatorIndex == -1) { throw new IllegalArgumentException("No !/ in spec '" + spec + "'"); } try { new URL(spec.substring(0, separatorIndex)); return spec; } catch (MalformedURLException ex) { throw new IllegalArgumentException("Invalid spec URL '" + spec + "'", ex); } } private String getFileFromContext(URL context, String spec) { String file = context.getFile(); StringBuilder sb = new StringBuilder(file.length() + spec.length()); if (spec.startsWith("/")) { return sb.append(trimToJarRoot(file)).append(SEPARATOR).append(spec.substring(1)) .toString(); } if (file.endsWith("/")) { return sb.append(file).append(spec).toString(); } int lastSlashIndex = file.lastIndexOf('/'); if (lastSlashIndex == -1) { throw new IllegalArgumentException("No / found in context URL's file '" + file + "'"); } return sb.append(file.substring(0, lastSlashIndex + 1)).append(spec).toString(); } private String trimToJarRoot(String file) { int lastSeparatorIndex = file.lastIndexOf(SEPARATOR); if (lastSeparatorIndex == -1) { throw new IllegalArgumentException("No !/ found in context URL's file '" + file + "'"); } return file.substring(0, lastSeparatorIndex); } private void setFile(URL context, String file) { setURL(context, JAR_PROTOCOL, null, -1, null, null, normalize(file), null, null); } private String normalize(String file) { int afterLastSeparatorIndex = file.lastIndexOf(SEPARATOR) + SEPARATOR.length(); String afterSeparator = file.substring(afterLastSeparatorIndex); afterSeparator = replaceParentDir(afterSeparator); afterSeparator = replaceCurrentDir(afterSeparator); return new StringBuilder(afterLastSeparatorIndex + afterSeparator.length()) .append(file.substring(0, afterLastSeparatorIndex)).append(afterSeparator).toString(); } String replaceParentDir(String file) { int parentDirIndex; while ((parentDirIndex = file.indexOf("/../")) >= 0) { int precedingSlashIndex = file.lastIndexOf('/', parentDirIndex - 1); if (precedingSlashIndex >= 0) { file = file.substring(0, precedingSlashIndex) + file.substring(parentDirIndex + 3); } else { file = file.substring(parentDirIndex + 4); } } return file; } private String replaceCurrentDir(String file) { return file.replace("/./", "/"); } @Override protected int hashCode(URL u) { return hashCode(u.getProtocol(), u.getFile()); } private int hashCode(String protocol, String file) { int result = (protocol == null ? 0 : protocol.hashCode()); int separatorIndex = file.indexOf(SEPARATOR); if (separatorIndex == -1) { return result + file.hashCode(); } String source = file.substring(0, separatorIndex); String entry = canonicalize(file.substring(separatorIndex + 2)); try { result += new URL(source).hashCode(); } catch (MalformedURLException ex) { result += source.hashCode(); } result += entry.hashCode(); return result; } @Override protected boolean sameFile(URL u1, URL u2) { if (!"jar".equals(u1.getProtocol()) || !"jar".equals(u2.getProtocol())) { return false; } int separator1 = u1.getFile().indexOf(SEPARATOR); int separator2 = u2.getFile().indexOf(SEPARATOR); if (separator1 == -1 || separator2 == -1) { return super.sameFile(u1, u2); } String nested1 = u1.getFile().substring(separator1 + SEPARATOR.length()); String nested2 = u2.getFile().substring(separator2 + SEPARATOR.length()); if (!nested1.equals(nested2)) { String canonical1 = canonicalize(nested1); String canonical2 = canonicalize(nested2); if (!canonical1.equals(canonical2)) { return false; } } String root1 = u1.getFile().substring(0, separator1); String root2 = u2.getFile().substring(0, separator2); try { return super.sameFile(new URL(root1), new URL(root2)); } catch (MalformedURLException ex) { // Continue } return super.sameFile(u1, u2); } private String canonicalize(String path) { return path.replace(SEPARATOR, "/"); } public JarFile getRootJarFileFromUrl(URL url) throws IOException { String spec = url.getFile(); int separatorIndex = spec.indexOf(SEPARATOR); if (separatorIndex == -1) { throw new MalformedURLException("Jar URL does not contain !/ separator"); } String name = spec.substring(0, separatorIndex); return getRootJarFile(name); } private JarFile getRootJarFile(String name) throws IOException { try { if (!name.startsWith(FILE_PROTOCOL)) { throw new IllegalStateException("Not a file URL"); } String path = name.substring(FILE_PROTOCOL.length()); File file = FileUtils.file(path); Map cache = rootFileCache.get(); JarFile result = (cache == null ? null : cache.get(file)); if (result == null) { result = new JarFile(file); addToRootFileCache(file, result); } return result; } catch (Exception ex) { throw new IOException("Unable to open root Jar file '" + name + "'", ex); } } /** * Add the given {@link JarFile} to the root file cache. * @param sourceFile the source file to add * @param jarFile the jar file. */ static void addToRootFileCache(File sourceFile, JarFile jarFile) { Map cache = rootFileCache.get(); if (cache == null) { cache = new ConcurrentHashMap<>(16); rootFileCache = new SoftReference<>(cache); } cache.put(sourceFile, jarFile); } /** * Set if a generic static exception can be thrown when a URL cannot be connected. * This optimization is used during class loading to save creating lots of exceptions * which are then swallowed. * @param useFastConnectionExceptions if fast connection exceptions can be used. */ public static void setUseFastConnectionExceptions(boolean useFastConnectionExceptions) { JarURLConnection.setUseFastExceptions(useFastConnectionExceptions); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/jar/JarEntry.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.security.CodeSigner; import java.security.cert.Certificate; import java.util.jar.Attributes; import java.util.jar.Manifest; /** * Extended variant of {@link java.util.jar.JarEntry} returned by {@link JarFile}s. * * @author Phillip Webb */ public class JarEntry extends java.util.jar.JarEntry implements FileHeader { private Certificate[] certificates; private CodeSigner[] codeSigners; private final JarFile jarFile; private long localHeaderOffset; JarEntry(JarFile jarFile, CentralDirectoryFileHeader header) { super(header.getName().toString()); this.jarFile = jarFile; this.localHeaderOffset = header.getLocalHeaderOffset(); setCompressedSize(header.getCompressedSize()); setMethod(header.getMethod()); setCrc(header.getCrc()); setSize(header.getSize()); setExtra(header.getExtra()); setComment(header.getComment().toString()); setSize(header.getSize()); setTime(header.getTime()); } @Override public boolean hasName(String name, String suffix) { return getName().length() == name.length() + suffix.length() && getName().startsWith(name) && getName().endsWith(suffix); } /** * Return a {@link URL} for this {@link JarEntry}. * @return the URL for the entry * @throws MalformedURLException if the URL is not valid */ URL getUrl() throws MalformedURLException { return new URL(this.jarFile.getUrl(), getName()); } @Override public Attributes getAttributes() throws IOException { Manifest manifest = this.jarFile.getManifest(); return (manifest == null ? null : manifest.getAttributes(getName())); } @Override public Certificate[] getCertificates() { if (this.jarFile.isSigned() && this.certificates == null) { this.jarFile.setupEntryCertificates(this); } return this.certificates; } @Override public CodeSigner[] getCodeSigners() { if (this.jarFile.isSigned() && this.codeSigners == null) { this.jarFile.setupEntryCertificates(this); } return this.codeSigners; } void setCertificates(java.util.jar.JarEntry entry) { this.certificates = entry.getCertificates(); this.codeSigners = entry.getCodeSigners(); } @Override public long getLocalHeaderOffset() { return this.localHeaderOffset; } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/jar/JarEntryFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; /** * Interface that can be used to filter and optionally rename jar entries. * * @author Phillip Webb */ public interface JarEntryFilter { /** * Apply the jar entry filter. * @param name the current entry name. This may be different that the original entry * name if a previous filter has been applied * @return the new name of the entry or {@code null} if the entry should not be * included. */ AsciiBytes apply(AsciiBytes name); } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/jar/JarFile.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import com.alipay.sofa.ark.loader.data.RandomAccessData; import com.alipay.sofa.ark.loader.data.RandomAccessData.ResourceAccess; import com.alipay.sofa.ark.loader.data.RandomAccessDataFile; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.net.MalformedURLException; import java.net.URL; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; import java.util.Enumeration; import java.util.Iterator; import java.util.Properties; import java.util.jar.JarInputStream; import java.util.jar.Manifest; import java.util.zip.ZipEntry; /** * Extended variant of {@link java.util.jar.JarFile} that behaves in the same way but * offers the following additional functionality. *
    *
  • A nested {@link JarFile} can be {@link #getNestedJarFile(ZipEntry) obtained} based * on any directory entry.
  • *
  • A nested {@link JarFile} can be {@link #getNestedJarFile(ZipEntry) obtained} for * embedded JAR files (as long as their entry is not compressed).
  • *
* * @author Phillip Webb */ public class JarFile extends java.util.jar.JarFile { private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; private static final String POM_PROPERTIES = "pom.properties"; private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs"; private static final String HANDLERS_PACKAGE = "com.alipay.sofa.ark.loader"; private static final AsciiBytes META_INF = new AsciiBytes("META-INF/"); private static final AsciiBytes SIGNATURE_FILE_EXTENSION = new AsciiBytes(".SF"); private final RandomAccessDataFile rootFile; private final String pathFromRoot; private final RandomAccessData data; private final JarFileType type; private URL url; private JarFileEntries entries; private SoftReference manifest; private boolean signed; /** * Create a new {@link JarFile} backed by the specified file. * @param file the root jar file * @throws IOException if the file cannot be read */ public JarFile(File file) throws IOException { this(new RandomAccessDataFile(file)); } /** * Create a new {@link JarFile} backed by the specified file. * @param file the root jar file * @throws IOException if the file cannot be read */ JarFile(RandomAccessDataFile file) throws IOException { this(file, "", file, JarFileType.DIRECT); } /** * Private constructor used to create a new {@link JarFile} either directly or from a * nested entry. * @param rootFile the root jar file * @param pathFromRoot the name of this file * @param data the underlying data * @param type the type of the jar file * @throws IOException if the file cannot be read */ private JarFile(RandomAccessDataFile rootFile, String pathFromRoot, RandomAccessData data, JarFileType type) throws IOException { this(rootFile, pathFromRoot, data, null, type); } private JarFile(RandomAccessDataFile rootFile, String pathFromRoot, RandomAccessData data, JarEntryFilter filter, JarFileType type) throws IOException { super(rootFile.getFile()); this.rootFile = rootFile; this.pathFromRoot = pathFromRoot; CentralDirectoryParser parser = new CentralDirectoryParser(); this.entries = parser.addVisitor(new JarFileEntries(this, filter)); parser.addVisitor(centralDirectoryVisitor()); this.data = parser.parse(data, filter == null); this.type = type; } private CentralDirectoryVisitor centralDirectoryVisitor() { return new CentralDirectoryVisitor() { @Override public void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) { } @Override public void visitFileHeader(CentralDirectoryFileHeader fileHeader, int dataOffset) { AsciiBytes name = fileHeader.getName(); if (name.startsWith(META_INF) && name.endsWith(SIGNATURE_FILE_EXTENSION)) { JarFile.this.signed = true; } } @Override public void visitEnd() { } }; } protected final RandomAccessDataFile getRootJarFile() { return this.rootFile; } RandomAccessData getData() { return this.data; } @Override public Manifest getManifest() throws IOException { Manifest manifest = (this.manifest == null ? null : this.manifest.get()); if (manifest == null) { if (this.type == JarFileType.NESTED_DIRECTORY) { try (JarFile jarFile = new JarFile(this.getRootJarFile())) { manifest = jarFile.getManifest(); } } else { try (InputStream inputStream = getInputStream(MANIFEST_NAME, ResourceAccess.ONCE)) { if (inputStream == null) { return null; } manifest = new Manifest(inputStream); } } this.manifest = new SoftReference<>(manifest); } return manifest; } public Properties getPomProperties() throws IOException { Enumeration entries = this.entries(); Properties p = new Properties(); while (entries.hasMoreElements()) { java.util.jar.JarEntry jarEntry = entries.nextElement(); if (jarEntry.getName().endsWith(POM_PROPERTIES)) { try (InputStream is = this.getInputStream(jarEntry)) { p.load(is); return p; } } } return p; } @Override public Enumeration entries() { final Iterator iterator = this.entries.iterator(); return new Enumeration() { @Override public boolean hasMoreElements() { return iterator.hasNext(); } @Override public java.util.jar.JarEntry nextElement() { return iterator.next(); } }; } @Override public JarEntry getJarEntry(String name) { return (JarEntry) getEntry(name); } public boolean containsEntry(String name) { return this.entries.containsEntry(name); } @Override public ZipEntry getEntry(String name) { return this.entries.getEntry(name); } @Override public synchronized InputStream getInputStream(ZipEntry ze) throws IOException { return getInputStream(ze, ResourceAccess.PER_READ); } public InputStream getInputStream(ZipEntry ze, ResourceAccess access) throws IOException { if (ze instanceof JarEntry) { return this.entries.getInputStream((JarEntry) ze, access); } return getInputStream(ze == null ? null : ze.getName(), access); } InputStream getInputStream(String name, ResourceAccess access) throws IOException { return this.entries.getInputStream(name, access); } /** * Return a nested {@link JarFile} loaded from the specified entry. * @param entry the zip entry * @return a {@link JarFile} for the entry * @throws IOException if the nested jar file cannot be read */ public synchronized JarFile getNestedJarFile(final ZipEntry entry) throws IOException { return getNestedJarFile((JarEntry) entry); } /** * Return a nested {@link JarFile} loaded from the specified entry. * @param entry the zip entry * @return a {@link JarFile} for the entry * @throws IOException if the nested jar file cannot be read */ public synchronized JarFile getNestedJarFile(JarEntry entry) throws IOException { try { return createJarFileFromEntry(entry); } catch (Exception ex) { throw new IOException("Unable to open nested jar file '" + entry.getName() + "'", ex); } } private JarFile createJarFileFromEntry(JarEntry entry) throws IOException { if (entry.isDirectory()) { return createJarFileFromDirectoryEntry(entry); } return createJarFileFromFileEntry(entry); } private JarFile createJarFileFromDirectoryEntry(JarEntry entry) throws IOException { final AsciiBytes sourceName = new AsciiBytes(entry.getName()); JarEntryFilter filter = new JarEntryFilter() { @Override public AsciiBytes apply(AsciiBytes name) { if (name.startsWith(sourceName) && !name.equals(sourceName)) { return name.substring(sourceName.length()); } return null; } }; return new JarFile(this.rootFile, this.pathFromRoot + "!/" + entry.getName().substring(0, sourceName.length() - 1), this.data, filter, JarFileType.NESTED_DIRECTORY); } private JarFile createJarFileFromFileEntry(JarEntry entry) throws IOException { if (entry.getMethod() != ZipEntry.STORED) { throw new IllegalStateException( "Unable to open nested entry '" + entry.getName() + "'. It has been compressed and nested " + "jar files must be stored without compression. Please check the " + "mechanism used to create your executable jar file"); } RandomAccessData entryData = this.entries.getEntryData(entry.getName()); return new JarFile(this.rootFile, this.pathFromRoot + "!/" + entry.getName(), entryData, JarFileType.NESTED_JAR); } @Override public int size() { return (int) this.data.getSize(); } @Override public void close() throws IOException { super.close(); this.rootFile.close(); } /** * Return a URL that can be used to access this JAR file. NOTE: the specified URL * cannot be serialized and or cloned. * @return the URL * @throws MalformedURLException if the URL is malformed */ public URL getUrl() throws MalformedURLException { if (this.url == null) { Handler handler = new Handler(this); String file = this.rootFile.getFile().toURI() + this.pathFromRoot + "!/"; file = file.replace("file:////", "file://"); // Fix UNC paths this.url = new URL("jar", "", -1, file, handler); } return this.url; } @Override public String toString() { return getName(); } @Override public String getName() { String str = this.rootFile.getFile().toString(); StringBuilder sb = new StringBuilder(str.length() + this.pathFromRoot.length()); return sb.append(str).append(this.pathFromRoot).toString(); } boolean isSigned() { return this.signed; } void setupEntryCertificates(JarEntry entry) { // Fallback to JarInputStream to obtain certificates, not fast but hopefully not // happening that often. try { JarInputStream inputStream = new JarInputStream(getData().getInputStream( ResourceAccess.ONCE)); try { java.util.jar.JarEntry certEntry = inputStream.getNextJarEntry(); while (certEntry != null) { inputStream.closeEntry(); if (entry.getName().equals(certEntry.getName())) { setCertificates(entry, certEntry); } setCertificates(getJarEntry(certEntry.getName()), certEntry); certEntry = inputStream.getNextJarEntry(); } } finally { inputStream.close(); } } catch (IOException ex) { throw new IllegalStateException(ex); } } private void setCertificates(JarEntry entry, java.util.jar.JarEntry certEntry) { if (entry != null) { entry.setCertificates(certEntry); } } public void clearCache() { this.entries.clearCache(); } protected String getPathFromRoot() { return this.pathFromRoot; } JarFileType getType() { return this.type; } /** * Register a {@literal 'java.protocol.handler.pkgs'} property so that a * {@link URLStreamHandler} will be located to deal with jar URLs. */ public static void registerUrlProtocolHandler() { String handlers = System.getProperty(PROTOCOL_HANDLER, ""); System.setProperty(PROTOCOL_HANDLER, ("".equals(handlers) ? HANDLERS_PACKAGE : handlers + "|" + HANDLERS_PACKAGE)); resetCachedUrlHandlers(); } /** * Reset any cached handlers just in case a jar protocol has already been used. We * reset the handler by trying to set a null {@link URLStreamHandlerFactory} which * should have no effect other than clearing the handlers cache. */ private static void resetCachedUrlHandlers() { try { URL.setURLStreamHandlerFactory(null); } catch (Error ex) { // Ignore } } /** * The type of a {@link JarFile}. */ enum JarFileType { DIRECT, NESTED_DIRECTORY, NESTED_JAR } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/jar/JarFileEntries.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import com.alipay.sofa.ark.loader.data.RandomAccessData; import com.alipay.sofa.ark.loader.data.RandomAccessData.ResourceAccess; import java.io.IOException; import java.io.InputStream; import java.util.*; import java.util.zip.ZipEntry; /** * Provides access to entries from a {@link JarFile}. In order to reduce memory * consumption entry details are stored using int arrays. The {@code hashCodes} array * stores the hash code of the entry name, the {@code centralDirectoryOffsets} provides * the offset to the central directory record and {@code positions} provides the original * order position of the entry. The arrays are stored in hashCode order so that a binary * search can be used to find a name. *

* A typical Spring Boot application will have somewhere in the region of 10,500 entries * which should consume about 122K. * * @author Phillip Webb */ public class JarFileEntries implements CentralDirectoryVisitor, Iterable { private static final long LOCAL_FILE_HEADER_SIZE = 30; private static final String SLASH = "/"; private static final String NO_SUFFIX = ""; protected static final int ENTRY_CACHE_SIZE = 25; private final JarFile jarFile; private final JarEntryFilter filter; private RandomAccessData centralDirectoryData; private int size; private int[] hashCodes; private int[] centralDirectoryOffsets; private int[] positions; private final Map entriesCache = Collections .synchronizedMap(new LinkedHashMap( 16, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { if (JarFileEntries.this.jarFile .isSigned()) { return false; } return size() >= ENTRY_CACHE_SIZE; } }); public JarFileEntries(JarFile jarFile, JarEntryFilter filter) { this.jarFile = jarFile; this.filter = filter; } @Override public void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) { int maxSize = endRecord.getNumberOfRecords(); this.centralDirectoryData = centralDirectoryData; this.hashCodes = new int[maxSize]; this.centralDirectoryOffsets = new int[maxSize]; this.positions = new int[maxSize]; } @Override public void visitFileHeader(CentralDirectoryFileHeader fileHeader, int dataOffset) { AsciiBytes name = applyFilter(fileHeader.getName()); if (name != null) { add(name, fileHeader, dataOffset); } } private void add(AsciiBytes name, CentralDirectoryFileHeader fileHeader, int dataOffset) { //NOPMD this.hashCodes[this.size] = name.hashCode(); this.centralDirectoryOffsets[this.size] = dataOffset; this.positions[this.size] = this.size; this.size++; } @Override public void visitEnd() { sort(0, this.size - 1); int[] positions = this.positions; this.positions = new int[positions.length]; for (int i = 0; i < this.size; i++) { this.positions[positions[i]] = i; } } private void sort(int left, int right) { // Quick sort algorithm, uses hashCodes as the source but sorts all arrays if (left < right) { int pivot = this.hashCodes[left + (right - left) / 2]; int i = left; int j = right; while (i <= j) { while (this.hashCodes[i] < pivot) { i++; } while (this.hashCodes[j] > pivot) { j--; } if (i <= j) { swap(i, j); i++; j--; } } if (left < j) { sort(left, j); } if (right > i) { sort(i, right); } } } private void swap(int i, int j) { swap(this.hashCodes, i, j); swap(this.centralDirectoryOffsets, i, j); swap(this.positions, i, j); } private void swap(int[] array, int i, int j) { int temp = array[i]; array[i] = array[j]; array[j] = temp; } @Override public Iterator iterator() { return new EntryIterator(); } public boolean containsEntry(String name) { return getEntry(name, FileHeader.class, true) != null; } public JarEntry getEntry(String name) { return getEntry(name, JarEntry.class, true); } public InputStream getInputStream(String name, ResourceAccess access) throws IOException { FileHeader entry = getEntry(name, FileHeader.class, false); return getInputStream(entry, access); } public InputStream getInputStream(FileHeader entry, ResourceAccess access) throws IOException { if (entry == null) { return null; } InputStream inputStream = getEntryData(entry).getInputStream(access); if (entry.getMethod() == ZipEntry.DEFLATED) { inputStream = new ZipInflaterInputStream(inputStream, (int) entry.getSize()); } return inputStream; } public RandomAccessData getEntryData(String name) throws IOException { FileHeader entry = getEntry(name, FileHeader.class, false); if (entry == null) { return null; } return getEntryData(entry); } private RandomAccessData getEntryData(FileHeader entry) throws IOException { // aspectjrt-1.7.4.jar has a different ext bytes length in the // local directory to the central directory. We need to re-read // here to skip them RandomAccessData data = this.jarFile.getData(); byte[] localHeader = Bytes.get(data.getSubsection(entry.getLocalHeaderOffset(), LOCAL_FILE_HEADER_SIZE)); long nameLength = Bytes.littleEndianValue(localHeader, 26, 2); long extraLength = Bytes.littleEndianValue(localHeader, 28, 2); return data.getSubsection(entry.getLocalHeaderOffset() + LOCAL_FILE_HEADER_SIZE + nameLength + extraLength, entry.getCompressedSize()); } private T getEntry(String name, Class type, boolean cacheEntry) { int hashCode = AsciiBytes.hashCode(name); T entry = getEntry(hashCode, name, NO_SUFFIX, type, cacheEntry); if (entry == null) { hashCode = AsciiBytes.hashCode(hashCode, SLASH); entry = getEntry(hashCode, name, SLASH, type, cacheEntry); } return entry; } private T getEntry(int hashCode, String name, String suffix, Class type, boolean cacheEntry) { int index = getFirstIndex(hashCode); while (index >= 0 && index < this.size && this.hashCodes[index] == hashCode) { T entry = getEntry(index, type, cacheEntry); if (entry.hasName(name, suffix)) { return entry; } index++; } return null; } @SuppressWarnings("unchecked") private T getEntry(int index, Class type, boolean cacheEntry) { try { FileHeader cached = this.entriesCache.get(index); FileHeader entry = (cached != null ? cached : CentralDirectoryFileHeader .fromRandomAccessData(this.centralDirectoryData, this.centralDirectoryOffsets[index], this.filter)); if (CentralDirectoryFileHeader.class.equals(entry.getClass()) && type.equals(JarEntry.class)) { entry = new JarEntry(this.jarFile, (CentralDirectoryFileHeader) entry); } if (cacheEntry && cached != entry) { this.entriesCache.put(index, entry); } return (T) entry; } catch (IOException ex) { throw new IllegalStateException(ex); } } private int getFirstIndex(int hashCode) { int index = Arrays.binarySearch(this.hashCodes, 0, this.size, hashCode); if (index < 0) { return -1; } while (index > 0 && this.hashCodes[index - 1] == hashCode) { index--; } return index; } public void clearCache() { this.entriesCache.clear(); } private AsciiBytes applyFilter(AsciiBytes name) { return (this.filter == null ? name : this.filter.apply(name)); } /** * Iterator for contained entries. */ private class EntryIterator implements Iterator { private int index = 0; @Override public boolean hasNext() { return this.index < JarFileEntries.this.size; } @Override public JarEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } int entryIndex = JarFileEntries.this.positions[this.index]; this.index++; return getEntry(entryIndex, JarEntry.class, false); } @Override public void remove() { throw new UnsupportedOperationException("remove"); } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/jar/JarURLConnection.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import com.alipay.sofa.ark.loader.data.RandomAccessData.ResourceAccess; import java.io.*; import java.net.*; import java.security.Permission; /** * {@link java.net.JarURLConnection} used to support {@link JarFile#getUrl()}. * * @author Phillip Webb * @author Andy Wilkinson * @author Rostyslav Dudka */ final public class JarURLConnection extends java.net.JarURLConnection { private static ThreadLocal useFastExceptions = new ThreadLocal<>(); private static final FileNotFoundException FILE_NOT_FOUND_EXCEPTION = new FileNotFoundException( "Jar file or entry not found"); private static final IllegalStateException NOT_FOUND_CONNECTION_EXCEPTION = new IllegalStateException( FILE_NOT_FOUND_EXCEPTION); private static final String SEPARATOR = "!/"; private static final URL EMPTY_JAR_URL; static { try { EMPTY_JAR_URL = new URL("jar:", null, 0, "file:!/", new URLStreamHandler() { @Override protected URLConnection openConnection(URL u) { // Stub URLStreamHandler to prevent the wrong JAR Handler from being // Instantiated and cached. return null; } }); } catch (MalformedURLException ex) { throw new IllegalStateException(ex); } } private static final JarEntryName EMPTY_JAR_ENTRY_NAME = new JarEntryName(""); private static final String READ_ACTION = "read"; private static final JarURLConnection NOT_FOUND_CONNECTION = JarURLConnection .notFound(); private final JarFile jarFile; private Permission permission; private URL jarFileUrl; private final JarEntryName jarEntryName; private JarEntry jarEntry; private JarURLConnection(URL url, JarFile jarFile, JarEntryName jarEntryName) throws IOException { // What we pass to super is ultimately ignored super(EMPTY_JAR_URL); this.url = url; this.jarFile = jarFile; this.jarEntryName = jarEntryName; } @Override public void connect() throws IOException { if (this.jarFile == null) { throw FILE_NOT_FOUND_EXCEPTION; } if (!this.jarEntryName.isEmpty() && this.jarEntry == null) { this.jarEntry = this.jarFile.getJarEntry(getEntryName()); if (this.jarEntry == null) { throwFileNotFound(this.jarEntryName, this.jarFile); } } this.connected = true; } @Override public JarFile getJarFile() throws IOException { connect(); return this.jarFile; } @Override public URL getJarFileURL() { if (this.jarFile == null) { throw NOT_FOUND_CONNECTION_EXCEPTION; } if (this.jarFileUrl == null) { this.jarFileUrl = buildJarFileUrl(); } return this.jarFileUrl; } private URL buildJarFileUrl() { try { String spec = this.jarFile.getUrl().getFile(); if (spec.endsWith(SEPARATOR)) { spec = spec.substring(0, spec.length() - SEPARATOR.length()); } if (!spec.contains(SEPARATOR)) { return new URL(spec); } return new URL("jar:" + spec); } catch (MalformedURLException ex) { throw new IllegalStateException(ex); } } @Override public JarEntry getJarEntry() throws IOException { if (this.jarEntryName == null || this.jarEntryName.isEmpty()) { return null; } connect(); return this.jarEntry; } @Override public String getEntryName() { if (this.jarFile == null) { throw NOT_FOUND_CONNECTION_EXCEPTION; } return this.jarEntryName.toString(); } @Override public InputStream getInputStream() throws IOException { if (this.jarFile == null) { throw FILE_NOT_FOUND_EXCEPTION; } if (this.jarEntryName.isEmpty() && this.jarFile.getType() == JarFile.JarFileType.DIRECT) { throw new IOException("no entry name specified"); } connect(); InputStream inputStream = (this.jarEntryName.isEmpty() ? this.jarFile.getData() .getInputStream(ResourceAccess.ONCE) : this.jarFile.getInputStream(this.jarEntry)); if (inputStream == null) { throwFileNotFound(this.jarEntryName, this.jarFile); } return inputStream; } private void throwFileNotFound(Object entry, JarFile jarFile) throws FileNotFoundException { if (Boolean.TRUE.equals(useFastExceptions.get())) { throw FILE_NOT_FOUND_EXCEPTION; } throw new FileNotFoundException("JAR entry " + entry + " not found in " + jarFile.getName()); } @Override public int getContentLength() { long length = getContentLengthLong(); if (length > Integer.MAX_VALUE) { return -1; } return (int) length; } @Override public long getContentLengthLong() { if (this.jarFile == null) { return -1; } try { if (this.jarEntryName.isEmpty()) { return this.jarFile.size(); } JarEntry entry = getJarEntry(); return (entry == null ? -1 : (int) entry.getSize()); } catch (IOException ex) { return -1; } } @Override public Object getContent() throws IOException { connect(); return (this.jarEntryName.isEmpty() ? this.jarFile : super.getContent()); } @Override public String getContentType() { return (this.jarEntryName == null ? null : this.jarEntryName.getContentType()); } @Override public Permission getPermission() throws IOException { if (this.jarFile == null) { throw FILE_NOT_FOUND_EXCEPTION; } if (this.permission == null) { this.permission = new FilePermission(this.jarFile.getRootJarFile().getFile().getPath(), READ_ACTION); } return this.permission; } @Override public long getLastModified() { if (this.jarFile == null || this.jarEntryName.isEmpty()) { return 0; } try { JarEntry entry = getJarEntry(); return (entry == null ? 0 : entry.getTime()); } catch (IOException ex) { return 0; } } static void setUseFastExceptions(boolean useFastExceptions) { JarURLConnection.useFastExceptions.set(useFastExceptions); } static JarURLConnection get(URL url, JarFile jarFile) throws IOException { String spec = extractFullSpec(url, jarFile.getPathFromRoot()); int separator; int index = 0; while ((separator = spec.indexOf(SEPARATOR, index)) > 0) { String entryName = spec.substring(index, separator); JarEntry jarEntry = jarFile.getJarEntry(entryName); if (jarEntry == null) { return JarURLConnection.notFound(jarFile, JarEntryName.get(entryName)); } jarFile = jarFile.getNestedJarFile(jarEntry); index = separator + SEPARATOR.length(); } JarEntryName jarEntryName = JarEntryName.get(spec, index); if (Boolean.TRUE.equals(useFastExceptions.get())) { if (!jarEntryName.isEmpty() && !jarFile.containsEntry(jarEntryName.toString())) { return NOT_FOUND_CONNECTION; } } return new JarURLConnection(url, jarFile, jarEntryName); } private static String extractFullSpec(URL url, String pathFromRoot) { String file = url.getFile(); int separatorIndex = file.indexOf(SEPARATOR); if (separatorIndex < 0) { return ""; } int specIndex = separatorIndex + SEPARATOR.length() + pathFromRoot.length(); return file.substring(specIndex); } private static JarURLConnection notFound() { try { return notFound(null, null); } catch (IOException ex) { throw new IllegalStateException(ex); } } private static JarURLConnection notFound(JarFile jarFile, JarEntryName jarEntryName) throws IOException { if (Boolean.TRUE.equals(useFastExceptions.get())) { return NOT_FOUND_CONNECTION; } return new JarURLConnection(null, jarFile, jarEntryName); } /** * A JarEntryName parsed from a URL String. */ static class JarEntryName { private final String name; private String contentType; JarEntryName(String spec) { this.name = decode(spec); } private String decode(String source) { if (source.indexOf('%') < 0) { return source; } ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length()); write(source, bos); // AsciiBytes is what is used to store the JarEntries so make it symmetric return AsciiBytes.toString(bos.toByteArray()); } private void write(String source, ByteArrayOutputStream outputStream) { int length = source.length(); for (int i = 0; i < length; i++) { int c = source.charAt(i); if (c > 127) { try { String encoded = URLEncoder.encode(String.valueOf((char) c), "UTF-8"); write(encoded, outputStream); } catch (UnsupportedEncodingException ex) { throw new IllegalStateException(ex); } } else { if (c == '%') { if ((i + 2) >= length) { throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\""); } c = decodeEscapeSequence(source, i); i += 2; } outputStream.write(c); } } } private char decodeEscapeSequence(String source, int i) { int hi = Character.digit(source.charAt(i + 1), 16); int lo = Character.digit(source.charAt(i + 2), 16); if (hi == -1 || lo == -1) { throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\""); } return ((char) ((hi << 4) + lo)); } @Override public String toString() { return this.name; } public boolean isEmpty() { return this.name.isEmpty(); } public String getContentType() { if (this.contentType == null) { this.contentType = deduceContentType(); } return this.contentType; } private String deduceContentType() { // Guess the content type, don't bother with streams as mark is not supported String type = (isEmpty() ? "x-java/jar" : null); type = (type != null ? type : guessContentTypeFromName(toString())); type = (type != null ? type : "content/unknown"); return type; } public static JarEntryName get(String spec) { return get(spec, 0); } public static JarEntryName get(String spec, int beginIndex) { if (spec.length() <= beginIndex) { return EMPTY_JAR_ENTRY_NAME; } return new JarEntryName(spec.substring(beginIndex)); } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/jar/JarUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.loader.archive.JarFileArchive; import com.alipay.sofa.ark.loader.util.ModifyPathUtils; import org.apache.maven.model.Model; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import java.io.*; import java.nio.file.Files; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.jar.JarEntry; public class JarUtils { private static final String CLASSPATH_ROOT_IDENTITY = "/target/classes"; private static final String TEST_CLASSPATH_ROOT_IDENTITY = "/target/test-classes"; private static final String TARGET_ROOT_IDENTITY = "/target/"; private static final String JAR_POM_PROPERTIES_RELATIVE_PATH = "maven-archiver/pom.properties"; private static final String JAR_ARTIFACT_ID = "artifactId"; private static final String JAR_POM_PROPERTIES = "pom.properties"; private static final String POM_FILE = "/pom.xml"; private static final String VERSION_REGEX = "^([0-9]+\\.)+.+"; private static final MavenXpp3Reader READER = new MavenXpp3Reader(); public static final String JAR_SEPARATOR = "!/"; public static final String JAR_SUFFIX = ".jar"; public static final String JAR_UNPACK = ".jar-unpack"; private static final Map> artifactIdCacheMap = new ConcurrentHashMap<>(); static File searchPomProperties(File dirOrFile) { if (dirOrFile == null || !dirOrFile.exists()) { return null; } if (dirOrFile.isFile() && JAR_POM_PROPERTIES.equals(dirOrFile.getName())) { return dirOrFile; } if (dirOrFile.isDirectory()) { File[] files = dirOrFile.listFiles(); if (files != null) { for (File file : files) { File result = searchPomProperties(file); if (result != null) { return result; } } } } return null; } static String getArtifactIdFromLocalClassPath(String fileClassPath) { String libraryFile = fileClassPath.replace("file:", ""); // 1. search pom.properties int classesRootIndex = libraryFile.endsWith(CLASSPATH_ROOT_IDENTITY) ? libraryFile .indexOf(CLASSPATH_ROOT_IDENTITY) : libraryFile.indexOf(CLASSPATH_ROOT_IDENTITY + "/"); int testClassesRootIndex = libraryFile.endsWith(TEST_CLASSPATH_ROOT_IDENTITY) ? libraryFile .indexOf(TEST_CLASSPATH_ROOT_IDENTITY) : libraryFile .indexOf(TEST_CLASSPATH_ROOT_IDENTITY + "/"); String pomPropertiesPath; String pomXmlPath = null; if (classesRootIndex != -1) { pomPropertiesPath = libraryFile.substring(0, classesRootIndex + TARGET_ROOT_IDENTITY.length()) + JAR_POM_PROPERTIES_RELATIVE_PATH; pomXmlPath = libraryFile.substring(0, classesRootIndex) + POM_FILE; } else if (testClassesRootIndex != -1) { pomPropertiesPath = libraryFile.substring(0, testClassesRootIndex + TARGET_ROOT_IDENTITY.length()) + JAR_POM_PROPERTIES_RELATIVE_PATH; pomXmlPath = libraryFile.substring(0, testClassesRootIndex) + POM_FILE; } else { // is not from test classpath, for example install uncompressed modules, just return null // search for pom.properties File pomPropertiesFile = searchPomProperties(FileUtils.file(libraryFile)); if (pomPropertiesFile != null && pomPropertiesFile.exists()) { pomPropertiesPath = pomPropertiesFile.getAbsolutePath(); } else { // not found pom.properties pomPropertiesPath = null; } } String artifactId = null; if (!StringUtils.isEmpty(pomPropertiesPath)) { try (InputStream inputStream = Files.newInputStream(FileUtils.file(pomPropertiesPath) .toPath())) { Properties properties = new Properties(); properties.load(inputStream); artifactId = properties.getProperty(JAR_ARTIFACT_ID); } catch (IOException e) { // ignore } } if (StringUtils.isEmpty(artifactId) && !StringUtils.isEmpty(pomXmlPath)) { try (FileReader fileReader = new FileReader(pomXmlPath)) { Model model = READER.read(fileReader); return model.getArtifactId(); } catch (Exception e) { // ignore } } return artifactId; } public static String parseArtifactId(String jarLocation) { // 1. /xxx/xxx/xx.jar!/ // 2. /xxx/xxx/xx.jar!/xxxx.class // 3. /xxx/xxx/xx.jar // 4. /xxx/xxx/xxx-bootstrap-1.0.0-ark-biz.jar!/BOOT-INF/lib/spring-boot-2.4.13.jar!/ // 5. /xxx/xxx-bootstrap-1.0.0-ark-biz.jar!/BOOT-INF/lib/sofa-ark-springboot-starter-2.1.1.jar!/META-INF/spring.factories // 6. /xxx/xxx/target/classes/xxxx.jar // 7. /xxx/xxx/target/test-classes/yyy/yyy/ // 8. /xxx/xxx/xxx-starter-1.0.0-SNAPSHOT.jar!/BOOT-INF/lib/xxx2-starter-1.1.4-SNAPSHOT-ark-biz.jar!/lib/xxx3-230605-sofa.jar!/ // 9. if is ark plugin, then return null to set declared default //10. use unpack model, file /xxx/xxx/xxx-0.0.1-ark-biz.jar-unpack/ // Check if it's an unpacked directory if (jarLocation.contains(JAR_UNPACK)) { // Try to extract artifactId from the unpacked directory String artifactId = parseArtifactIdFromUnpackedDir(jarLocation); if (artifactId != null) { return artifactId; } // If failed, fallback to extracting from directory name return doGetArtifactIdFromFileName(jarLocation); } // For non-unpacked paths, clean the jar location prefix and suffix if (jarLocation.contains(JAR_SUFFIX)) { jarLocation = jarLocation.substring(0, jarLocation.lastIndexOf(JAR_SUFFIX) + JAR_SUFFIX.length()); } if (jarLocation.startsWith("file:")) { jarLocation = jarLocation.substring("file:".length()); } // modify the path to suit WindowsOS jarLocation = ModifyPathUtils.modifyPath(jarLocation); String finalJarLocation = jarLocation; artifactIdCacheMap.computeIfAbsent(jarLocation, a -> { try { String artifactId; String[] as = a.split(JAR_SEPARATOR, -1); if (as.length == 1) { // no '!/' if (a.endsWith(".jar")) { artifactId = parseArtifactIdFromJar(a); if (StringUtils.isEmpty(artifactId)) { artifactId = doGetArtifactIdFromFileName(a); } } else { artifactId = getArtifactIdFromLocalClassPath(a); } } else { // contains one '!/' or more artifactId = parseArtifactIdFromJar(a); if (StringUtils.isEmpty(artifactId)) { artifactId = doGetArtifactIdFromFileName(a); } } return Optional.ofNullable(artifactId); } catch (IOException e) { throw new RuntimeException(String.format("Failed to parse artifact id from jar %s.", finalJarLocation), e); } }); return artifactIdCacheMap.get(jarLocation).orElse(null); } private static String doGetArtifactIdFromFileName(String jarLocation) { String[] jarInfos = jarLocation.split("/"); if (jarInfos.length == 0) { return null; } String artifactVersion = jarInfos[jarInfos.length - 1]; String[] artifactVersionInfos = artifactVersion.split("-"); List artifactInfos = new ArrayList<>(); boolean getVersion = false; for (String info : artifactVersionInfos) { if (!StringUtils.isEmpty(info) && info.matches(VERSION_REGEX)) { getVersion = true; break; } artifactInfos.add(info); } if (getVersion) { return String.join("-", artifactInfos); } // if can't find any version from jar name, then we just return null to paas the declared check return null; } private static String parseArtifactIdFromJar(String jarLocation) throws IOException { try (com.alipay.sofa.ark.loader.jar.JarFile jarFile = getNestedRootJarFromJarLocation(jarLocation)) { JarFileArchive jarFileArchive = new JarFileArchive(jarFile); return jarFileArchive.getPomProperties().getProperty(JAR_ARTIFACT_ID); } } public static com.alipay.sofa.ark.loader.jar.JarFile getNestedRootJarFromJarLocation(String jarLocation) throws IOException { // /xxx/xxx/xxx-starter-1.0.0-SNAPSHOT.jar!/BOOT-INF/lib/xxx2-starter-1.1.4-SNAPSHOT-ark-biz.jar!/lib/xxx3-230605-sofa.jar String[] js = jarLocation.split(JAR_SEPARATOR, -1); com.alipay.sofa.ark.loader.jar.JarFile rJarFile = new com.alipay.sofa.ark.loader.jar.JarFile( FileUtils.file(js[0])); for (int i = 1; i < js.length; i++) { String jPath = js[i]; if (StringUtils.isEmpty(jPath) || !jPath.endsWith(".jar")) { break; } try { JarEntry jarEntry = rJarFile.getJarEntry(jPath); rJarFile = rJarFile.getNestedJarFile(jarEntry); } catch (NullPointerException e) { throw new IOException( String.format("Failed to parse artifact id, jPath: %s", jPath), e); } } return rJarFile; } private static String parseArtifactIdFromUnpackedDir(String unpackDirPath) { File unpackDir = new File(unpackDirPath); if (!unpackDir.exists() || !unpackDir.isDirectory()) { return null; } // Look for pom.properties in maven-archiver directory File pomPropsFile = searchPomProperties(unpackDir); if (pomPropsFile != null && pomPropsFile.exists()) { try (InputStream inputStream = Files.newInputStream(pomPropsFile.toPath())) { Properties properties = new Properties(); properties.load(inputStream); String artifactId = properties.getProperty(JAR_ARTIFACT_ID); if (artifactId != null && !artifactId.isEmpty()) { return artifactId; } } catch (IOException e) { throw new RuntimeException(String.format( "Failed to parse artifact id from path %s.", unpackDirPath), e); } } return null; } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/jar/ZipInflaterInputStream.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; /** * {@link InflaterInputStream} that supports the writing of an extra "dummy" byte (which * is required with JDK 6) and returns accurate available() results. * * @author Phillip Webb */ public class ZipInflaterInputStream extends InflaterInputStream { private boolean extraBytesWritten; private int available; ZipInflaterInputStream(InputStream inputStream, int size) { super(inputStream, new Inflater(true), getInflaterBufferSize(size)); this.available = size; } @Override public int available() throws IOException { if (this.available < 0) { return super.available(); } return this.available; } @Override public int read(byte[] b, int off, int len) throws IOException { int result = super.read(b, off, len); if (result != -1) { this.available -= result; } return result; } @Override public void close() throws IOException { super.close(); this.inf.end(); } @Override protected void fill() throws IOException { try { super.fill(); } catch (EOFException ex) { if (this.extraBytesWritten) { throw ex; } this.len = 1; this.buf[0] = 0x0; this.extraBytesWritten = true; this.inf.setInput(this.buf, 0, this.len); } } private static int getInflaterBufferSize(long size) { // inflater likes some space size += 2; size = (size > 65536 ? 8192 : size); size = (size <= 0 ? 4096 : size); return (int) size; } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/main/java/com/alipay/sofa/ark/loader/util/ModifyPathUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.util; /** * @author sususama * @since 2023/5/8 */ public class ModifyPathUtils { /** When using SofaArk in the Windows environment, you will encounter a path error. * This tool class will judge the read file or the configured path, * and judge whether it is the file path of the Windows operating system. * If it is, this method will modify the path to suit WindowsOS, * otherwise the input path will be returned directly. * * @param path File Path * @return Modified file path */ public static String modifyPath(String path) { if (path.charAt(2) == ':') { path = path.substring(1); } return path; } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/bootstrap/ArkLauncherTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.bootstrap; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.loader.EmbedClassPathArchive; import com.alipay.sofa.ark.loader.archive.JarFileArchive; import com.alipay.sofa.ark.spi.archive.Archive; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import java.io.File; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static com.alipay.sofa.ark.bootstrap.ArkLauncher.main; import static org.junit.Assert.*; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; /** * @author yan * @version ArkLauncherTest.java, v 0.1 2023年10月17日 20:28 yan */ public class ArkLauncherTest { static MockedStatic managementFactoryMockedStatic; @BeforeClass public static void setup() { List mockArguments = new ArrayList<>(); String filePath = ClasspathLauncherTest.class.getClassLoader() .getResource("SampleClass.class").getPath(); String workingPath = new File(filePath).getParent(); mockArguments.add(String.format("-javaagent:%s", workingPath)); RuntimeMXBean runtimeMXBean = Mockito.mock(RuntimeMXBean.class); when(runtimeMXBean.getInputArguments()).thenReturn(mockArguments); managementFactoryMockedStatic = mockStatic(ManagementFactory.class); managementFactoryMockedStatic.when(ManagementFactory::getRuntimeMXBean).thenReturn(runtimeMXBean); } @AfterClass public static void tearDown() { managementFactoryMockedStatic.close(); } @Test public void testContainerClassLoader() throws Exception { URL url = this.getClass().getClassLoader().getResource("sample-springboot-fat-biz.jar"); URL[] agentUrl = ClassLoaderUtils.getAgentClassPath(); assertEquals(1, agentUrl.length); List urls = new ArrayList<>(); JarFileArchive jarFileArchive = new JarFileArchive(new File(url.getFile())); List archives = jarFileArchive.getNestedArchives(this::isNestedArchive); for (Archive archive : archives) { urls.add(archive.getUrl()); } urls.addAll(Arrays.asList(agentUrl)); EmbedClassPathArchive classPathArchive = new EmbedClassPathArchive( this.getClass().getCanonicalName(), null, urls.toArray(new URL[]{})); ArkLauncher arkLauncher = new ArkLauncher(classPathArchive); ClassLoader classLoader = arkLauncher.createContainerClassLoader(classPathArchive.getContainerArchive()); assertNotNull(classLoader); try { Class clazz = classLoader.loadClass("com.alipay.sofa.ark.bootstrap.ArkLauncher"); assertNotNull(clazz); clazz = classLoader.loadClass("SampleClass"); assertNotNull(clazz); } catch (Exception e) { assertTrue("loadClass class failed ", false); } assertThrows(ClassNotFoundException.class, () -> classLoader.loadClass("NotExistClass")); } protected boolean isNestedArchive(Archive.Entry entry) { return entry.isDirectory() ? entry.getName().equals("BOOT-INF/classes/") : entry.getName() .startsWith("BOOT-INF/lib/"); } @Test(expected = Exception.class) public void testMain() throws Exception { main(null); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/bootstrap/ClasspathLauncherTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.bootstrap; import com.alipay.sofa.ark.bootstrap.ClasspathLauncher.ClassPathArchive; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.loader.DirectoryBizArchive; import com.alipay.sofa.ark.loader.EmbedClassPathArchive; import com.alipay.sofa.ark.loader.archive.JarFileArchive; import com.alipay.sofa.ark.spi.archive.Archive; import com.alipay.sofa.ark.spi.archive.BizArchive; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static com.alipay.sofa.ark.common.util.ClassLoaderUtils.getAgentClassPath; import static org.junit.Assert.*; import static org.mockito.Mockito.when; /** * @author qilong.zql * @since 0.6.0 */ public class ClasspathLauncherTest { static MockedStatic managementFactoryMockedStatic; @BeforeClass public static void setup() { List mockArguments = new ArrayList<>(); String filePath = ClasspathLauncherTest.class.getClassLoader() .getResource("SampleClass.class").getPath(); String workingPath = FileUtils.file(filePath).getParent(); mockArguments.add(String.format("-javaagent:%s", workingPath)); RuntimeMXBean runtimeMXBean = Mockito.mock(RuntimeMXBean.class); when(runtimeMXBean.getInputArguments()).thenReturn(mockArguments); managementFactoryMockedStatic = Mockito.mockStatic(ManagementFactory.class); managementFactoryMockedStatic.when(ManagementFactory::getRuntimeMXBean).thenReturn(runtimeMXBean); } @AfterClass public static void tearDown() { managementFactoryMockedStatic.close(); } @Test public void testFilterAgentClasspath() throws Exception { URL url = this.getClass().getClassLoader().getResource("sample-biz.jar"); URL[] agentUrl = getAgentClassPath(); assertEquals(1, agentUrl.length); List urls = new ArrayList<>(); urls.add(url); urls.addAll(Arrays.asList(agentUrl)); ClassPathArchive classPathArchive = new ClassPathArchive( this.getClass().getCanonicalName(), null, urls.toArray(new URL[] {})); List bizArchives = classPathArchive.getBizArchives(); assertEquals(1, bizArchives.size()); assertEquals(2, urls.size()); } @Test public void testSpringBootFatJar() throws Exception { URL url = this.getClass().getClassLoader().getResource("sample-springboot-fat-biz.jar"); URL[] agentUrl = getAgentClassPath(); assertEquals(1, agentUrl.length); List urls = new ArrayList<>(); JarFileArchive jarFileArchive = new JarFileArchive(FileUtils.file(url.getFile())); List archives = jarFileArchive.getNestedArchives(this::isNestedArchive); for (Archive archive : archives) { urls.add(archive.getUrl()); } urls.addAll(Arrays.asList(agentUrl)); EmbedClassPathArchive classPathArchive = new EmbedClassPathArchive( this.getClass().getCanonicalName(), null, urls.toArray(new URL[]{})); List bizArchives = classPathArchive.getBizArchives(); assertEquals(0, bizArchives.size()); assertNotNull(classPathArchive.getContainerArchive()); assertEquals(1, classPathArchive.getPluginArchives().size()); assertEquals(archives.size() + 1, urls.size()); assertEquals(3, classPathArchive.getConfClasspath().size()); URLClassLoader classLoader = new URLClassLoader(classPathArchive.getContainerArchive().getUrls()); try { Class clazz = classLoader.loadClass("com.alipay.sofa.ark.bootstrap.ArkLauncher"); assertNotNull(clazz); } catch (Exception e) { assertTrue("loadClass class failed ", false); } } protected boolean isNestedArchive(Archive.Entry entry) { return entry.isDirectory() ? entry.getName().equals("BOOT-INF/classes/") : entry.getName() .startsWith("BOOT-INF/lib/"); } @Test public void testConfClasspath() throws IOException { ClassLoader classLoader = this.getClass().getClassLoader(); ClassPathArchive classPathArchive = new ClassPathArchive( this.getClass().getCanonicalName(), null, ClassLoaderUtils.getURLs(classLoader)); List confClasspath = classPathArchive.getConfClasspath(); assertEquals(3, confClasspath.size()); } @Test public void testFromSurefire() throws IOException { ClassLoader classLoader = this.getClass().getClassLoader(); ClassPathArchive classPathArchive = new ClassPathArchive( this.getClass().getCanonicalName(), null, ClassLoaderUtils.getURLs(classLoader)); URL url1 = Mockito.mock(URL.class); URL url2 = Mockito.mock(URL.class); URL url3 = Mockito.mock(URL.class); when(url1.getFile()).thenReturn("surefirebooter17233117990150815938.jar"); when(url2.getFile()).thenReturn("org.jacoco.agent-0.8.4-runtime.jar"); when(url3.getFile()).thenReturn("byte-buddy-agent-1.10.15.jar"); assertTrue(classPathArchive.fromSurefire(new URL[] { url1, url2, url3 })); List urls2 = classPathArchive.getConfClasspath(); urls2.add(url2); urls2.add(url3); assertFalse(classPathArchive.fromSurefire(urls2.toArray(new URL[0]))); } @Test public void testOtherMethods() throws IOException { URL url = this.getClass().getClassLoader().getResource("sample-biz.jar"); URL[] agentUrl = getAgentClassPath(); assertEquals(1, agentUrl.length); List urls = new ArrayList<>(); urls.add(url); urls.addAll(Arrays.asList(agentUrl)); ClassPathArchive classPathArchive = new ClassPathArchive( this.getClass().getCanonicalName(), null, urls.toArray(new URL[] {})); try { classPathArchive.getUrl(); } catch (Exception e) { } try { classPathArchive.getManifest(); } catch (Exception e) { } try { classPathArchive.getNestedArchives(null); } catch (Exception e) { } try { classPathArchive.getNestedArchive(null); } catch (Exception e) { } try { classPathArchive.getInputStream(null); } catch (Exception e) { } try { classPathArchive.iterator(); } catch (Exception e) { } assertTrue(classPathArchive.createDirectoryBizModuleArchive().getClass() .equals(DirectoryBizArchive.class)); URL url2 = new URL("file://aa"); assertArrayEquals(new URL[] { url2 }, classPathArchive.filterBizUrls(new URL[] { agentUrl[0], url, url2 })); URL surefireJarURL = this.getClass().getClassLoader() .getResource("sample-biz-surefire.jar"); assertArrayEquals(new URL[] { url2, new URL("file://b") }, classPathArchive.parseClassPathFromSurefireBoot(surefireJarURL)); } @Test public void testBaseExecutableArchiveLauncher() { BaseExecutableArchiveLauncher baseExecutableArchiveLauncher = new BaseExecutableArchiveLauncher() { @Override protected String getMainClass() throws Exception { return null; } }; assertNotNull(baseExecutableArchiveLauncher.getExecutableArchive()); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/bootstrap/MainMethodRunnerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.bootstrap; import org.junit.Assert; import org.junit.Before; import org.junit.Test; /** * @author qilong.zql * @since 0.1.0 */ public class MainMethodRunnerTest { private static int count; @Before public void init() { MainMethodRunnerTest.count = 0; } @Test public void testRunner() { MainMethodRunner mainMethodRunner = new MainMethodRunner(MainClass.class.getName(), new String[] { "10" }, null); try { mainMethodRunner.run(); Assert.assertTrue(MainMethodRunnerTest.count == 10); } catch (Exception e) { Assert.assertNull(e); } } public static class MainClass { public static void main(String[] args) { if (args.length > 0) { MainMethodRunnerTest.count += Integer.valueOf(args[0]); } } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/DirectoryBizArchiveTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader; import com.alipay.sofa.ark.spi.archive.Archive; import com.alipay.sofa.ark.spi.archive.Archive.Entry; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Field; import java.net.MalformedURLException; import java.net.URL; import java.util.Iterator; import static com.alipay.sofa.ark.spi.constant.Constants.ARK_BIZ_MARK_ENTRY; import static java.util.Collections.singletonList; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class DirectoryBizArchiveTest { private DirectoryBizArchive directoryBizArchive; @Before public void setUp() throws MalformedURLException { directoryBizArchive = new DirectoryBizArchive("a", "b", new URL[] { new URL("file://a") }); } @Test public void testDirectoryBizArchive() throws Exception { assertFalse(directoryBizArchive.isTestMode()); assertEquals("a", directoryBizArchive.getClassName()); assertEquals("b", directoryBizArchive.getMethodName()); assertArrayEquals(new URL[]{new URL("file://a")}, directoryBizArchive.getUrls()); assertTrue(directoryBizArchive.isEntryExist(entry -> !entry.isDirectory() && entry.getName().equals(ARK_BIZ_MARK_ENTRY))); assertEquals(6, directoryBizArchive.getManifest().getMainAttributes().size()); try { directoryBizArchive.getUrl(); assertTrue(false); } catch (Exception e) { } try { directoryBizArchive.getNestedArchives(entry -> false); assertTrue(false); } catch (Exception e) { } try { directoryBizArchive.getInputStream(null); assertTrue(false); } catch (Exception e) { } try { directoryBizArchive.iterator(); assertTrue(false); } catch (Exception e) { } Archive nestedArchive = directoryBizArchive.getNestedArchive(new Entry() { @Override public boolean isDirectory() { return false; } @Override public String getName() { return ARK_BIZ_MARK_ENTRY; } }); Field field = JarBizArchive.class.getDeclaredField("archive"); field.setAccessible(true); assertNull(field.get(nestedArchive)); } @Test public void testJarBizArchive() throws Exception { Archive archive = mock(Archive.class); JarBizArchive jarBizArchive = new JarBizArchive(archive); Iterator iterator = singletonList(new Entry() { @Override public boolean isDirectory() { return false; } @Override public String getName() { return "lib/export/a"; } }).iterator(); when(archive.iterator()).thenReturn((Iterator) iterator); when(archive.getUrl()).thenReturn(new URL("file://a")); when(archive.getNestedArchive(any())).thenReturn(archive); assertArrayEquals(new URL[] { new URL("file://a"), new URL("file://a") }, jarBizArchive.getExportUrls()); assertNull(jarBizArchive.getInputStream(null)); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/DirectoryContainerArchiveTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader; import org.junit.Test; import java.net.URL; import static org.junit.Assert.assertTrue; public class DirectoryContainerArchiveTest { private DirectoryContainerArchive directoryContainerArchive; @Test public void testDirectoryContainerArchive() throws Exception { directoryContainerArchive = new DirectoryContainerArchive(new URL[] { new URL("file://a") }); try { directoryContainerArchive.getUrl(); assertTrue(false); } catch (Exception e) { } try { directoryContainerArchive.getManifest(); assertTrue(false); } catch (Exception e) { } try { directoryContainerArchive.getNestedArchives(null); assertTrue(false); } catch (Exception e) { } try { directoryContainerArchive.getNestedArchive(null); assertTrue(false); } catch (Exception e) { } try { directoryContainerArchive.getInputStream(null); assertTrue(false); } catch (Exception e) { } try { directoryContainerArchive.iterator(); assertTrue(false); } catch (Exception e) { } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/EmbedClassPathArchiveTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.spi.archive.BizArchive; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Biz; import org.junit.Assert; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.boot.loader.archive.Archive; import org.springframework.boot.loader.archive.JarFileArchive; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Objects; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; /** * @author bingjie.lbj * @since 0.1.0 */ public class EmbedClassPathArchiveTest { @Test public void testGetContainerArchive() throws Exception { ClassLoader cl = Thread.currentThread().getContextClassLoader(); URL springbootFatJar = cl.getResource("sample-springboot-fat-biz.jar"); JarFileArchive jarFileArchive = new JarFileArchive(FileUtils.file(springbootFatJar.getFile())); Iterator archives = jarFileArchive.getNestedArchives(this::isNestedArchive,null); List urls = new ArrayList<>(); while (archives.hasNext()){ urls.add(archives.next().getUrl()); } EmbedClassPathArchive archive = new EmbedClassPathArchive( "com.alipay.sofa.ark.sample.springbootdemo.SpringbootDemoApplication", "main", urls.toArray(new URL[] {})); assertTrue(archive.getContainerArchive().getUrls().length != 0); assertTrue(archive.getConfClasspath().size() != 0); assertTrue(archive.getBizArchives().size() == 0); assertTrue(archive.getPluginArchives().size() == 1); URLClassLoader classLoader = new URLClassLoader(archive.getContainerArchive().getUrls()); try { Class clazz = classLoader.loadClass("com.alipay.sofa.ark.container.ArkContainer"); assertTrue(clazz != null); } catch (Exception e) { assertTrue("loadClass class failed ", false); } } protected boolean isNestedArchive(Archive.Entry entry) { return entry.isDirectory() ? Objects.equals(entry.getName(), "BOOT-INF/classes/") : entry .getName().startsWith("BOOT-INF/lib/"); } @Test public void testStaticCombineGetBizArchives() throws Exception { ClassLoader cl = Thread.currentThread().getContextClassLoader(); URL springbootFatJar = cl.getResource("static-combine-demo.jar"); JarFileArchive jarFileArchive = new JarFileArchive(FileUtils.file(springbootFatJar.getFile())); Iterator archives = jarFileArchive.getNestedArchives(this::isNestedArchive,null); List urls = new ArrayList<>(); while (archives.hasNext()){ urls.add(archives.next().getUrl()); } EmbedClassPathArchive archive = new EmbedClassPathArchive("com.alipay.sofa.ark.sample.springbootdemo.SpringbootDemoApplication", "main", urls.toArray(new URL[] {})); List bizArchives = archive.getBizArchives(); Assert.assertFalse(bizArchives==null||bizArchives.isEmpty()); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/ExecutableArkBizJarTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader; import com.alipay.sofa.ark.spi.archive.Archive; import org.junit.Before; import org.junit.Test; import java.util.Iterator; import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class ExecutableArkBizJarTest { private Archive archive = mock(Archive.class); private ExecutableArkBizJar executableArkBizJar = new ExecutableArkBizJar(archive); @Before public void setUp() { when(archive.iterator()).thenReturn(mock(Iterator.class)); } @Test public void testExecutableArkBizJar() throws Exception { assertNull(executableArkBizJar.getManifest()); assertNull(executableArkBizJar.getInputStream(null)); assertNull(executableArkBizJar.getNestedArchive(null)); try { executableArkBizJar.getContainerArchive(); assertTrue(false); } catch (RuntimeException e) { } assertEquals(0, executableArkBizJar.getConfClasspath().size()); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/ExplodedBizArchiveTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader; import com.alipay.sofa.ark.common.util.FileUtils; import org.junit.Assert; import org.junit.Test; import java.io.File; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import static org.apache.commons.io.FileUtils.deleteQuietly; /** * * @author bingjie.lbj */ public class ExplodedBizArchiveTest { @Test public void testCreate() throws IOException { ClassLoader cl = Thread.currentThread().getContextClassLoader(); URL arkBizJar = cl.getResource("sample-biz-withjar.jar"); File unpack = FileUtils.unzip(FileUtils.file(arkBizJar.getFile()), arkBizJar.getFile() + "-unpack"); ExplodedBizArchive archive = new ExplodedBizArchive(unpack); Assert.assertNotNull(archive.getManifest()); Assert.assertNotNull(archive.getUrl()); try { archive.getInputStream(null); Assert.assertTrue(false); } catch (UnsupportedOperationException e){ Assert.assertTrue(true); } try { archive.isEntryExist(entry -> true); Assert.assertTrue(false); } catch (UnsupportedOperationException e){ Assert.assertTrue(true); } try { archive.getNestedArchive(null); Assert.assertTrue(false); } catch (UnsupportedOperationException e){ Assert.assertTrue(true); } try { archive.getNestedArchives(null); Assert.assertTrue(false); } catch (UnsupportedOperationException e){ Assert.assertTrue(true); } try { archive.iterator(); Assert.assertTrue(false); } catch (UnsupportedOperationException e){ Assert.assertTrue(true); } Assert.assertEquals(archive.getManifest().getMainAttributes().getValue("Ark-Biz-Name"), "sofa-ark-sample-springboot-ark"); Assert.assertEquals(archive.getUrls().length, 3); ClassLoader bizClassLoader = new URLClassLoader(archive.getUrls()); Class mainClass = null; Class logger = null; try { mainClass = bizClassLoader .loadClass("com.alipay.sofa.ark.sample.springbootdemo.SpringbootDemoApplication"); logger = bizClassLoader.loadClass("org.slf4j.Logger"); } catch (ClassNotFoundException exception) { } Assert.assertNotNull(logger); Assert.assertNotNull(mainClass); } @Test public void testCloseManifestFileStream() throws IOException { ClassLoader cl = Thread.currentThread().getContextClassLoader(); URL arkBizJar = cl.getResource("sample-biz-withjar.jar"); File unpack = FileUtils.unzip(FileUtils.file(arkBizJar.getFile()), arkBizJar.getFile() + "-testdelete-unpack"); ExplodedBizArchive archive = new ExplodedBizArchive(unpack); Assert.assertNotNull(archive.getManifest()); File file = new File(unpack, "META-INF/MANIFEST.MF"); Assert.assertTrue(file.delete()); deleteQuietly(unpack); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/archive/ExplodedArchiveTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.archive; import com.alipay.sofa.ark.spi.archive.Archive; import org.junit.Before; import org.junit.Test; import java.io.File; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class ExplodedArchiveTest { private ExplodedArchive explodedArchive; private String archiveTestDirPath = this.getClass().getClassLoader() .getResource("./exploded-archive-test/") .getFile(); @Before public void setUp() { explodedArchive = new ExplodedArchive(new File(archiveTestDirPath)); } @Test public void testGetMethods() throws Exception { assertTrue(explodedArchive.getUrl().getFile().endsWith("test-classes/exploded-archive-test/")); assertTrue(explodedArchive.getManifest() != null); List nestedArchives = explodedArchive.getNestedArchives(entry -> !entry.getName().contains("META-INF")); assertEquals(2, nestedArchives.size()); String nestedArchivesStr = nestedArchives.toString(); assertTrue(nestedArchivesStr.contains("/example-jarinjarinjar.jar!/")); assertTrue(nestedArchivesStr.contains("/sample-biz.jar!/")); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/archive/JarFileArchiveTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.archive; import com.alipay.sofa.ark.loader.archive.JarFileArchive.JarFileEntry; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.IOException; import java.util.jar.JarEntry; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class JarFileArchiveTest { private JarFileArchive jarFileArchive; private String jarFilePath = this.getClass().getClassLoader() .getResource("./sample-springboot-fat-biz.jar") .getFile(); @Before public void setUp() throws IOException { jarFileArchive = new JarFileArchive(new File(jarFilePath)); } @Test public void testGetMethods() throws Exception { assertTrue(jarFileArchive.getManifest() != null); assertEquals(50, jarFileArchive.getNestedArchives(entry -> entry.getName().contains(".jar")).size()); JarEntry jarEntry = new JarEntry("BOOT-INF/lib/slf4j-api-1.7.21.jar"); jarEntry.setComment("UNPACK:xxx"); assertTrue(jarFileArchive.getNestedArchive(new JarFileEntry(jarEntry)).getUrl().getFile().endsWith("slf4j-api-1.7.21.jar")); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/jar/HandlerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import org.junit.Test; import java.net.MalformedURLException; import java.net.URL; import static org.junit.Assert.assertEquals; public class HandlerTest { private URL url = this.getClass().getClassLoader() .getResource("sample-biz-withjar.jar"); private Handler handler = new Handler(); @Test(expected = MalformedURLException.class) public void testOpenConnectionWithIOException() throws Exception { handler.openConnection(this.getClass().getClassLoader() .getResource("sample-biz-withjar.jar")); } @Test(expected = NullPointerException.class) public void testOpenConnectionWithNPE() throws Exception { handler.openConnection(this.getClass().getClassLoader() .getResource("sample-biz-withjar.jar!/lib")); } @Test(expected = IllegalArgumentException.class) public void testParseURLWithIllegalSpec() throws Exception { handler.parseURL(url, "/", 0, 1); } @Test(expected = SecurityException.class) public void testParseURLWithEmptySpec() throws Exception { handler.parseURL(url, "/", 0, 0); } @Test public void testReplaceParentDir() throws Exception { assertEquals("../a", handler.replaceParentDir("/../../a")); assertEquals("../", handler.replaceParentDir("/../../")); assertEquals("aaa", handler.replaceParentDir("/../aaa/../aaa")); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/jar/JarEntryTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import org.junit.Before; import org.junit.Test; import java.io.File; import java.net.URL; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; public class JarEntryTest { private URL url; private JarEntry jarEntry; @Before public void setUp() throws Exception { url = this.getClass().getClassLoader().getResource("sample-biz-withjar.jar"); jarEntry = new JarEntry(new JarFile(new File(url.getPath())), new CentralDirectoryFileHeader(new byte[64], 0, new AsciiBytes("lib"), null, new AsciiBytes("mycomment"), 0)); } @Test public void testGetters() throws Exception { assertEquals("jar:" + url + "!/lib", jarEntry.getUrl().toString()); assertNull(jarEntry.getAttributes()); assertNull(jarEntry.getCertificates()); assertNull(jarEntry.getCodeSigners()); jarEntry.setCertificates(new java.util.jar.JarEntry("a")); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/jar/JarFileTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import org.junit.Test; import java.io.File; import java.io.IOException; import java.net.URL; import static com.alipay.sofa.ark.loader.jar.JarFile.JarFileType.DIRECT; import static org.junit.Assert.assertEquals; public class JarFileTest { @Test public void testSetupEntryCertificates() throws IOException { URL url = this.getClass().getClassLoader().getResource("sample-biz.jar"); JarFile jarFile = new JarFile(new File(url.getPath())); assertEquals(7485, jarFile.size()); assertEquals("jar:" + url.toString() + "!/", jarFile.getUrl().toString()); jarFile.setupEntryCertificates(new JarEntry(jarFile, new CentralDirectoryFileHeader( new byte[64], 0, new AsciiBytes("lib"), null, new AsciiBytes("mycomment"), 0))); jarFile.clearCache(); assertEquals(DIRECT, jarFile.getType()); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/jar/JarURLConnectionTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import com.alipay.sofa.ark.loader.jar.JarURLConnection.JarEntryName; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.net.URL; import static com.alipay.sofa.ark.loader.jar.JarURLConnection.JarEntryName.get; import static com.alipay.sofa.ark.loader.jar.JarURLConnection.get; import static org.junit.Assert.*; public class JarURLConnectionTest { private JarURLConnection jarURLConnection; private URL url = this.getClass().getClassLoader() .getResource("sample-biz-withjar.jar"); @Before public void setUp() throws Exception { jarURLConnection = get(url, new JarFile(new File(url.getPath()))); } @Test public void testGetJarFileURL() throws IOException { assertTrue(jarURLConnection.getJarFileURL().getFile().endsWith("/sample-biz-withjar.jar")); assertNull(jarURLConnection.getJarEntry()); jarURLConnection = get(new URL( "file://a/b/sample-biz-withjar.jar!/lib/slf4j-api-1.7.30.jar!/"), new JarFile(new File( url.getPath()))); assertEquals("com.alipay.sofa.ark.loader.data.RandomAccessDataFile$DataInputStream", jarURLConnection.getInputStream().getClass().getName()); assertNull(jarURLConnection.getJarEntry()); assertEquals("", jarURLConnection.getEntryName()); } @Test public void testGetContentLength() throws Exception { assertEquals(52949, jarURLConnection.getContentLength()); Field field = JarURLConnection.class.getDeclaredField("jarEntryName"); field.setAccessible(true); field.set(jarURLConnection, new JarEntryName("!/lib/slf4j-api-1.7.30.jar!/")); assertEquals(-1, jarURLConnection.getContentLength()); } @Test public void testGetContent() throws IOException { assertEquals(JarFile.class, jarURLConnection.getContent().getClass()); assertEquals("x-java/jar", jarURLConnection.getContentType()); } @Test public void testGetLastModified() throws Exception { assertEquals(0, jarURLConnection.getLastModified()); Field field = JarURLConnection.class.getDeclaredField("jarEntryName"); field.setAccessible(true); field.set(jarURLConnection, new JarEntryName("!/lib/slf4j-api-1.7.30.jar!/")); assertEquals(0, jarURLConnection.getLastModified()); } @Test public void testJarEntryName() { JarEntryName jarEntryName = get(url.toString()); String javaVersion = System.getProperty("java.version"); if (javaVersion.startsWith("1.8")) { assertEquals("content/unknown", jarEntryName.getContentType()); } else { assertEquals("application/java-archive", jarEntryName.getContentType()); } if (javaVersion.startsWith("1.8")) { assertEquals("content/unknown", jarEntryName.getContentType()); } else { assertEquals("application/java-archive", jarEntryName.getContentType()); } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/jar/JarUtilsParseArtifactIdFromUnpackedDirTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import org.junit.Test; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.Properties; import static org.junit.Assert.*; public class JarUtilsParseArtifactIdFromUnpackedDirTest { @Test public void testParseArtifactIdFromUnpackedDir_StandardLocation() throws IOException { // Create a temporary directory to simulate unpacked jar File tempDir = com.alipay.sofa.ark.common.util.FileUtils.createTempDir("test-unpack"); try { // Create the standard maven-archiver/pom.properties location File mavenArchiverDir = new File(tempDir, "META-INF/maven-archiver"); mavenArchiverDir.mkdirs(); File pomPropertiesFile = new File(mavenArchiverDir, "pom.properties"); try (FileWriter writer = new FileWriter(pomPropertiesFile)) { Properties props = new Properties(); props.setProperty("artifactId", "test-artifact"); props.store(writer, "Test pom.properties file"); } // Test the method String result = JarUtilsTestHelper.parseArtifactIdFromUnpackedDir(tempDir .getAbsolutePath()); assertEquals("test-artifact", result); } finally { org.apache.commons.io.FileUtils.deleteQuietly(tempDir); } } @Test public void testParseArtifactIdFromUnpackedDir_NonStandardLocation() throws IOException { // Create a temporary directory to simulate unpacked jar File tempDir = com.alipay.sofa.ark.common.util.FileUtils .createTempDir("test-unpack-nonstandard"); try { // Create pom.properties in a nested directory (non-standard location) File nestedDir = new File(tempDir, "some/nested/path"); nestedDir.mkdirs(); File pomPropertiesFile = new File(nestedDir, "pom.properties"); try (FileWriter writer = new FileWriter(pomPropertiesFile)) { Properties props = new Properties(); props.setProperty("artifactId", "test-artifact-nested"); props.store(writer, "Test pom.properties file in nested location"); } // Test the method - should find the pom.properties via recursive search String result = JarUtilsTestHelper.parseArtifactIdFromUnpackedDir(tempDir .getAbsolutePath()); assertEquals("test-artifact-nested", result); } finally { org.apache.commons.io.FileUtils.deleteQuietly(tempDir); } } @Test public void testParseArtifactIdFromUnpackedDir_DirectoryDoesNotExist() { // Test with non-existent directory String result = JarUtilsTestHelper .parseArtifactIdFromUnpackedDir("/non/existent/directory"); assertNull(result); } @Test public void testParseArtifactIdFromUnpackedDir_EmptyPomProperties() throws IOException { // Create a temporary directory with empty pom.properties File tempDir = com.alipay.sofa.ark.common.util.FileUtils.createTempDir("test-unpack-empty"); try { // Create the standard maven-archiver/pom.properties location with empty artifactId File mavenArchiverDir = new File(tempDir, "META-INF/maven-archiver"); mavenArchiverDir.mkdirs(); File pomPropertiesFile = new File(mavenArchiverDir, "pom.properties"); try (FileWriter writer = new FileWriter(pomPropertiesFile)) { Properties props = new Properties(); props.setProperty("artifactId", ""); // Empty artifactId props.store(writer, "Test pom.properties file with empty artifactId"); } // Test the method - should return null for empty artifactId String result = JarUtilsTestHelper.parseArtifactIdFromUnpackedDir(tempDir .getAbsolutePath()); assertNull(result); } finally { org.apache.commons.io.FileUtils.deleteQuietly(tempDir); } } @Test public void testParseArtifactIdFromUnpackedDir_MissingArtifactIdProperty() throws IOException { // Create a temporary directory with pom.properties that doesn't have artifactId File tempDir = com.alipay.sofa.ark.common.util.FileUtils .createTempDir("test-unpack-missing-key"); try { // Create the standard maven-archiver/pom.properties location without artifactId File mavenArchiverDir = new File(tempDir, "META-INF/maven-archiver"); mavenArchiverDir.mkdirs(); File pomPropertiesFile = new File(mavenArchiverDir, "pom.properties"); try (FileWriter writer = new FileWriter(pomPropertiesFile)) { Properties props = new Properties(); props.setProperty("groupId", "com.test"); // Different property props.setProperty("version", "1.0.0"); props.store(writer, "Test pom.properties file without artifactId"); } // Test the method - should return null when artifactId property is missing String result = JarUtilsTestHelper.parseArtifactIdFromUnpackedDir(tempDir .getAbsolutePath()); assertNull(result); } finally { org.apache.commons.io.FileUtils.deleteQuietly(tempDir); } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/jar/JarUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import org.junit.Test; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.net.URL; import java.util.Properties; import static com.alipay.sofa.ark.loader.jar.JarUtils.getArtifactIdFromLocalClassPath; import static com.alipay.sofa.ark.loader.jar.JarUtils.searchPomProperties; import static org.junit.Assert.*; public class JarUtilsTest { @Test public void testSearchPomProperties() { assertNull(searchPomProperties(null)); assertNull(searchPomProperties(new File("/not-exists"))); URL url = this.getClass().getClassLoader().getResource("pom-properties/pom.properties"); File file = new File(url.getPath()); assertEquals(file, searchPomProperties(file)); url = this.getClass().getClassLoader().getResource("./"); file = new File(url.getPath()); assertTrue(searchPomProperties(file).getPath().endsWith("pom.properties")); } @Test public void testGetArtifactIdFromLocalClassPath() { assertNull(getArtifactIdFromLocalClassPath("/a/target/bbb")); URL url = this.getClass().getClassLoader().getResource(""); assertEquals("sofa-ark-archive", getArtifactIdFromLocalClassPath(url.getPath())); } @Test public void testParseArtifactId() { URL url = this.getClass().getClassLoader().getResource("sample-biz.jar"); String artifactId = JarUtils.parseArtifactId(url.getPath()); assertEquals("sofa-ark-sample-springboot-ark", artifactId); } @Test public void testParseArtifactId2() { URL url = this.getClass().getClassLoader().getResource("xxxxx.jar-unpack"); String artifactId = JarUtils.parseArtifactId(url.getPath()); assertEquals("xxxx-test", artifactId); } @Test public void testParseArtifactIdFromUnpackDirNameFallback() { File tempRoot = com.alipay.sofa.ark.common.util.FileUtils .createTempDir("test-unpack-fallback"); try { File unpackDir = new File(tempRoot, "demo-service-1.0.0.jar-unpack"); assertTrue(unpackDir.mkdirs()); String artifactId = JarUtils.parseArtifactId(normalizePath(unpackDir)); assertEquals("demo-service", artifactId); } finally { org.apache.commons.io.FileUtils.deleteQuietly(tempRoot); } } @Test public void testParseArtifactIdFromUnpackDirNameFallbackWhenArtifactIdMissing() throws IOException { File tempRoot = com.alipay.sofa.ark.common.util.FileUtils .createTempDir("test-unpack-fallback-missing-artifact-id"); try { File unpackDir = new File(tempRoot, "demo-service-1.0.0.jar-unpack"); File mavenArchiverDir = new File(unpackDir, "META-INF/maven-archiver"); assertTrue(mavenArchiverDir.mkdirs()); File pomPropertiesFile = new File(mavenArchiverDir, "pom.properties"); try (FileWriter writer = new FileWriter(pomPropertiesFile)) { Properties props = new Properties(); props.setProperty("groupId", "com.test"); props.setProperty("version", "1.0.0"); props.store(writer, "Test pom.properties file without artifactId"); } String artifactId = JarUtils.parseArtifactId(normalizePath(unpackDir)); assertEquals("demo-service", artifactId); } finally { org.apache.commons.io.FileUtils.deleteQuietly(tempRoot); } } private String normalizePath(File file) { return file.getAbsolutePath().replace(File.separatorChar, '/'); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/jar/JarUtilsTestHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import java.lang.reflect.Method; /** * Helper class to access private methods in JarUtils for testing purposes. */ public class JarUtilsTestHelper { /** * Call the private parseArtifactIdFromUnpackedDir method for testing. * * @param unpackDirPath the path to the unpacked directory * @return the parsed artifactId or null if not found */ public static String parseArtifactIdFromUnpackedDir(String unpackDirPath) { try { Method method = JarUtils.class.getDeclaredMethod("parseArtifactIdFromUnpackedDir", String.class); method.setAccessible(true); return (String) method.invoke(null, unpackDirPath); } catch (Exception e) { throw new RuntimeException("Failed to invoke parseArtifactIdFromUnpackedDir", e); } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/jar/ZipInflaterInputStreamTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.jar; import org.junit.Test; import java.io.ByteArrayInputStream; import java.lang.reflect.Field; import java.util.zip.InflaterInputStream; import static org.junit.Assert.assertEquals; public class ZipInflaterInputStreamTest { @Test public void testFill() throws Exception { ZipInflaterInputStream zipInflaterInputStream = new ZipInflaterInputStream( new ByteArrayInputStream(new byte[0]), 0); assertEquals(0, zipInflaterInputStream.available()); zipInflaterInputStream.fill(); Field field = ZipInflaterInputStream.class.getDeclaredField("extraBytesWritten"); field.setAccessible(true); assertEquals(true, field.get(zipInflaterInputStream)); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/test/base/BaseTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.test.base; import com.alipay.sofa.ark.loader.data.RandomAccessDataFile; import com.alipay.sofa.ark.loader.jar.AsciiBytes; import com.alipay.sofa.ark.loader.jar.Bytes; import org.apache.commons.io.FileUtils; import org.junit.AfterClass; import org.junit.BeforeClass; import java.io.*; import java.net.URL; import java.util.jar.JarOutputStream; import java.util.zip.CRC32; import java.util.zip.ZipEntry; /** * @author qilong.zql * @since 0.1.0 */ public abstract class BaseTest { public static final String TEMP_DIR = "temp-workspace"; public static final String TEMP_FILE = "temp-file.info"; public static final String TEMP_ZIP = "temp-fat-jar.jar"; public static final String TEST_ENTRY = "testEntry/"; public static final String TEST_ENTRY_COMMENT = "testComment"; public static final String TEST_ENTRY_EXTRA = "testExtra"; public static final byte[] CONSTANT_BYTE = new byte[] { '1', '1', '2', '2', '3', '3', '4', '4', '5', '5', '6', '6', '7', '7', '8', '8' }; @BeforeClass public static void startUp() throws IOException { generateFile(); generateZip(); } @AfterClass public static void shutDown() { cleanWorkspace(); } /** * 构建普通的文本文件 */ public static void generateFile() throws IOException { OutputStream outputStream = new FileOutputStream(getTempDemoFile()); try { outputStream.write(CONSTANT_BYTE, 0, CONSTANT_BYTE.length); } finally { outputStream.close(); } } /** * 构建 zip 文件 */ public static void generateZip() throws IOException { JarOutputStream jos = new JarOutputStream(new FileOutputStream(getTempDemoZip())); CRC32 crc = new CRC32(); /* name end with '/' indicates a directory */ jos.putNextEntry(new ZipEntry("META-INF/")); jos.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); jos.write(generateManifest()); jos.putNextEntry(new ZipEntry("lib/")); ZipEntry jarEntry = new ZipEntry("lib/junit-4.12.jar"); byte[] jarContent = fetchResource("junit-4.12.jar"); crc.update(jarContent); jarEntry.setMethod(ZipEntry.STORED); jarEntry.setSize(jarContent.length); jarEntry.setCrc(crc.getValue()); jos.putNextEntry(jarEntry); jos.write(jarContent); ZipEntry entryForTest = new ZipEntry(TEST_ENTRY); entryForTest.setComment(TEST_ENTRY_COMMENT); entryForTest.setExtra(TEST_ENTRY_EXTRA.getBytes()); jos.putNextEntry(entryForTest); jos.closeEntry(); jos.close(); } private static byte[] generateManifest() { AsciiBytes asciiBytes = new AsciiBytes("").append("k1: v1\n").append("k2: v2\n"); return asciiBytes.toString().getBytes(); } private static byte[] fetchResource(String resourceName) throws IOException { URL resource = BaseTest.class.getClassLoader().getResource(resourceName); RandomAccessDataFile dataFile = new RandomAccessDataFile( com.alipay.sofa.ark.common.util.FileUtils.file(resource.getFile())); return Bytes.get(dataFile); } public static File getTmpDir() { String tmpPath = System.getProperty("java.io.tmpdir"); return com.alipay.sofa.ark.common.util.FileUtils.file(tmpPath); } public static File getWorkspace() { File workSpace = new File(getTmpDir(), TEMP_DIR); if (!workSpace.exists()) { workSpace.mkdirs(); } return workSpace; } public static File getTempDemoFile() { return new File(getWorkspace(), TEMP_FILE); } public static File getTempDemoZip() { return new File(getWorkspace(), TEMP_ZIP); } public static boolean cleanWorkspace() { File workDir = new File(getTmpDir(), TEMP_DIR); return FileUtils.deleteQuietly(workDir); } public static boolean compareByteArray(byte[] a, byte[] b) { if (a == null && b == null) { return true; } if (a == null || b == null) { return false; } if (a.length != b.length) { return false; } for (int i = 0; i < a.length; ++i) { if (a[i] != b[i]) { return false; } } return true; } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/test/data/RandomAccessDataFileTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.test.data; import com.alipay.sofa.ark.loader.data.RandomAccessData; import com.alipay.sofa.ark.loader.data.RandomAccessDataFile; import com.alipay.sofa.ark.loader.test.base.BaseTest; import org.junit.Assert; import org.junit.Test; import java.io.IOException; import java.io.InputStream; /** * @author qilong.zql * @since 0.1.0 */ public class RandomAccessDataFileTest extends BaseTest { @Test public void testInputStream() throws IOException { RandomAccessDataFile testFile = new RandomAccessDataFile(getTempDemoFile()); InputStream is = null; try { is = testFile.getInputStream(RandomAccessData.ResourceAccess.PER_READ); byte[] bytes = new byte[20]; Assert.assertTrue(is.read(bytes) == 16); for (int i = 0; i < 16; ++i) { Assert.assertTrue((bytes[i] & 0xFF) == '1' + i / 2); } } catch (IOException e) { Assert.fail(e.getMessage()); } finally { if (is != null) { is.close(); } } } @Test public void testSize() { RandomAccessDataFile testFile = new RandomAccessDataFile(getTempDemoFile()); Assert.assertTrue(testFile.getSize() == 16); } @Test public void testSubsection() throws IOException { RandomAccessDataFile testFile = new RandomAccessDataFile(getTempDemoFile()); try { testFile.getSubsection(0, 17); Assert.fail("Should throws IndexOutOfBoundsException"); } catch (Exception ex) { Assert.assertTrue(ex instanceof IndexOutOfBoundsException); } RandomAccessData subData = testFile.getSubsection(2, 4); InputStream is = null; try { is = subData.getInputStream(RandomAccessData.ResourceAccess.ONCE); byte[] bytes = new byte[10]; Assert.assertTrue(is.read(bytes) == 4); for (int i = 0; i < 4; ++i) { Assert.assertTrue((bytes[i] & 0xFF) == '2' + i / 2); } } catch (Exception e) { Assert.fail(e.getMessage()); } finally { if (is != null) { is.close(); } } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/test/jar/AsciiBytesTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.test.jar; import com.alipay.sofa.ark.loader.jar.AsciiBytes; import org.junit.Test; import static org.junit.Assert.*; /** * @author qilong.zql * @since 0.1.0 */ public class AsciiBytesTest { public final String content = "SofaArk is a class-isolated container"; private AsciiBytes asciiBytes = new AsciiBytes(content); @Test public void testAsciiBytes() { assertTrue(asciiBytes.length() == content.length()); assertTrue(asciiBytes.startsWith(new AsciiBytes("SofaArk"))); assertTrue(asciiBytes.endsWith(new AsciiBytes("container"))); assertTrue(asciiBytes.toString().equals(content)); assertTrue(asciiBytes.substring(8, 10).endsWith(new AsciiBytes("is"))); String suffix = "suffix"; AsciiBytes suffixAsciiBytes = new AsciiBytes(suffix); byte[] suffixBytes = suffix.getBytes(); asciiBytes.append(suffix).equals(content + suffix); asciiBytes.append(suffixAsciiBytes).equals(content + suffix); asciiBytes.append(suffixBytes).equals(content + suffix); } @Test public void testHashCode() { AsciiBytes asciiBytes = new AsciiBytes("" + (char) 0xffff + (char) -99 + (char) -255 + (char) -128 + (char) 127 + (char) 128 + (char) 255 + (char) 256 + content); assertEquals(-243313336, asciiBytes.hashCode()); assertNotEquals(new AsciiBytes(""), asciiBytes); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/test/jar/BytesTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.test.jar; import com.alipay.sofa.ark.loader.data.RandomAccessData; import com.alipay.sofa.ark.loader.data.RandomAccessDataFile; import com.alipay.sofa.ark.loader.jar.Bytes; import com.alipay.sofa.ark.loader.test.base.BaseTest; import org.junit.Assert; import org.junit.Test; import java.io.IOException; /** * @author qilong.zql * @since 0.1.0 */ public class BytesTest extends BaseTest { @Test public void testBytes() throws IOException { RandomAccessDataFile tempFile = new RandomAccessDataFile(getTempDemoFile()); byte[] content = Bytes.get(tempFile); Assert.assertTrue(content.length == CONSTANT_BYTE.length); for (int i = 0; i < CONSTANT_BYTE.length; ++i) { Assert.assertTrue(content[i] == CONSTANT_BYTE[i]); } byte[] bytes = new byte[3]; Bytes.fill(tempFile.getInputStream(RandomAccessData.ResourceAccess.ONCE), bytes); String.valueOf(bytes).equals("112"); long value = Bytes.littleEndianValue(CONSTANT_BYTE, 0, 4); Assert.assertTrue(value == ('2' << 24) + ('2' << 16) + ('1' << 8) + '1'); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/test/jar/CentralDirectoryEndRecordTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.test.jar; import com.alipay.sofa.ark.loader.data.RandomAccessData; import com.alipay.sofa.ark.loader.data.RandomAccessDataFile; import com.alipay.sofa.ark.loader.jar.CentralDirectoryEndRecord; import com.alipay.sofa.ark.loader.test.base.BaseTest; import org.junit.Test; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author qilong.zql * @since 0.1.0 */ public class CentralDirectoryEndRecordTest extends BaseTest { @Test public void testEOCD() throws IOException { RandomAccessDataFile dataFile = new RandomAccessDataFile(getTempDemoZip()); CentralDirectoryEndRecord eocd = new CentralDirectoryEndRecord(dataFile); assertTrue(eocd.isValid()); assertTrue(eocd.getStartOfArchive(dataFile) == 0); assertTrue(eocd.getNumberOfRecords() == 5); } @Test public void testWithInvalidFile() throws Exception { RandomAccessData randomAccessData = mock(RandomAccessData.class); when(randomAccessData.getInputStream(any())).thenReturn(mock(InputStream.class)); URL url = this.getClass().getClassLoader().getResource("example-jarinjarinjar.jar"); new CentralDirectoryEndRecord(new RandomAccessDataFile(new File(url.getPath()))); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/test/jar/CentralDirectoryFileHeaderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.test.jar; import com.alipay.sofa.ark.loader.data.RandomAccessData; import com.alipay.sofa.ark.loader.data.RandomAccessDataFile; import com.alipay.sofa.ark.loader.jar.AsciiBytes; import com.alipay.sofa.ark.loader.jar.Bytes; import com.alipay.sofa.ark.loader.jar.CentralDirectoryEndRecord; import com.alipay.sofa.ark.loader.jar.CentralDirectoryFileHeader; import com.alipay.sofa.ark.loader.test.base.BaseTest; import org.junit.Test; import java.io.IOException; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author qilong.zql * @since 0.1.0 */ public class CentralDirectoryFileHeaderTest extends BaseTest { public final static int CENTRAL_DIRECTORY_HEADER_BASE_SIZE = 46; @Test public void testCDFH() throws IOException { RandomAccessDataFile dataFile = new RandomAccessDataFile(getTempDemoZip()); CentralDirectoryEndRecord eocd = new CentralDirectoryEndRecord(dataFile); RandomAccessData cdfhBlock = eocd.getCentralDirectory(dataFile); List cdfhList = new ArrayList<>(); int dataOffset = 0; for (int i = 0; i < eocd.getNumberOfRecords(); ++i) { CentralDirectoryFileHeader cdfh = new CentralDirectoryFileHeader(); cdfh.load(Bytes.get(cdfhBlock), dataOffset, null, 0, null); dataOffset += CENTRAL_DIRECTORY_HEADER_BASE_SIZE + cdfh.getName().length() + cdfh.getComment().length() + cdfh.getExtra().length; cdfhList.add(cdfh); } assertTrue(cdfhList.size() == 5); assertTrue(cdfhList.get(4).getName().toString().equals(TEST_ENTRY)); assertTrue(cdfhList.get(4).getComment().toString().equals(TEST_ENTRY_COMMENT)); assertTrue(compareByteArray(cdfhList.get(4).getExtra(), TEST_ENTRY_EXTRA.getBytes())); } @Test public void testOtherMethods() { CentralDirectoryFileHeader centralDirectoryFileHeader = new CentralDirectoryFileHeader( new byte[64], 0, new AsciiBytes("a/"), null, null, 0); assertEquals(true, centralDirectoryFileHeader.isDirectory()); CentralDirectoryFileHeader centralDirectoryFileHeader2 = centralDirectoryFileHeader.clone(); assertEquals(new AsciiBytes("a/"), centralDirectoryFileHeader2.getName()); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/test/jar/CentralDirectoryParserTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.test.jar; import com.alipay.sofa.ark.loader.data.RandomAccessData; import com.alipay.sofa.ark.loader.data.RandomAccessDataFile; import com.alipay.sofa.ark.loader.jar.*; import com.alipay.sofa.ark.loader.test.base.BaseTest; import org.junit.Assert; import org.junit.Test; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * @author qilong.zql * @since 0.1.0 */ public class CentralDirectoryParserTest extends BaseTest { @Test public void testParser() throws IOException { CentralDirectoryParser cdParser = new CentralDirectoryParser(); TestVisitor testVisitor = cdParser.addVisitor(new TestVisitor()); RandomAccessDataFile dataFile = new RandomAccessDataFile(getTempDemoZip()); cdParser.parse(dataFile, false); List cdfhList = testVisitor.getCdfhList(); Assert.assertTrue(testVisitor.getEntryNum() == 5); Assert.assertTrue(cdfhList.size() == 5); Assert.assertTrue(cdfhList.get(4).getName().toString().equals(TEST_ENTRY)); Assert.assertTrue(cdfhList.get(4).getComment().toString().equals(TEST_ENTRY_COMMENT)); Assert .assertTrue(compareByteArray(cdfhList.get(4).getExtra(), TEST_ENTRY_EXTRA.getBytes())); } public static class TestVisitor implements CentralDirectoryVisitor { public int entryNum; public List cdfhList = new ArrayList<>(); @Override public void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) { entryNum = endRecord.getNumberOfRecords(); } @Override public void visitFileHeader(CentralDirectoryFileHeader fileHeader, int dataOffset) { cdfhList.add(fileHeader); } @Override public void visitEnd() { } public int getEntryNum() { return entryNum; } public List getCdfhList() { return cdfhList; } } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/test/jar/JarFileTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.test.jar; import com.alipay.sofa.ark.loader.jar.JarEntry; import com.alipay.sofa.ark.loader.jar.JarFile; import com.alipay.sofa.ark.loader.test.base.BaseTest; import org.junit.Test; import java.io.IOException; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import static org.junit.Assert.assertTrue; /** * @author qilong.zql * @since 0.1.0 */ public class JarFileTest extends BaseTest { @Test public void testJarFile() throws IOException { JarFile jarFile = new JarFile(getTempDemoZip()); Manifest manifest = jarFile.getManifest(); assertTrue(manifest.getMainAttributes().getValue("k1").equals("v1")); assertTrue(manifest.getMainAttributes().getValue("k2").equals("v2")); assertTrue(jarFile.containsEntry(TEST_ENTRY)); ZipEntry zipEntry = jarFile.getEntry(TEST_ENTRY); assertTrue(zipEntry.getName().equals(TEST_ENTRY)); assertTrue(zipEntry.getComment().equals(TEST_ENTRY_COMMENT)); assertTrue(compareByteArray(zipEntry.getExtra(), TEST_ENTRY_EXTRA.getBytes())); JarEntry jarEntry = jarFile.getJarEntry("lib/junit-4.12.jar"); JarFile nestJarFile = jarFile.getNestedJarFile(jarEntry); Manifest nestManifest = nestJarFile.getManifest(); assertTrue(nestManifest.getMainAttributes().getValue("Implementation-Title") .equals("JUnit")); assertTrue(nestManifest.getMainAttributes().getValue("Implementation-Version") .equals("4.12")); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/test/jar/JarUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.test.jar; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.loader.jar.JarUtils; import com.google.common.io.Files; import org.junit.Test; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; import static com.alipay.sofa.ark.loader.jar.JarUtils.parseArtifactId; import static java.lang.String.format; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; public class JarUtilsTest { @Test public void getArtifactIdFromTestClassPath() throws IOException { URL url = this.getClass().getClassLoader().getResource("sample-biz-withjar.jar"); String artifactId = parseArtifactId(url.getPath()); assertEquals("sofa-ark-sample-springboot-ark", artifactId); } @Test public void getArtifactIdFromTestClassPath1() throws IOException { URL url = this.getClass().getClassLoader().getResource("SampleClass.class"); String artifactId = parseArtifactId(url.getPath()); assertEquals("sofa-ark-archive", artifactId); } @Test public void getArtifactIdFromClassPath() throws IOException, URISyntaxException { URL clazzURL = this.getClass().getClassLoader() .getResource("com/alipay/sofa/ark/loader/jar/JarUtils.class"); String artifactId = parseArtifactId(clazzURL.getPath()); assertEquals("sofa-ark-archive", artifactId); URL testClazzURL = this.getClass().getClassLoader() .getResource("com/alipay/sofa/ark/loader/test/jar/JarUtilsTest.class"); artifactId = parseArtifactId(testClazzURL.getPath()); assertEquals("sofa-ark-archive", artifactId); String path = this.getClass().getClassLoader().getResource("example-jarinjarinjar.jar") .getPath(); String artifactId1 = parseArtifactId(path + "!/lib"); assertEquals("example-client", artifactId1); URL fatJarURL = this.getClass().getClassLoader().getResource("sample-springboot-fat-biz.jar"); List urls = extractResourceURLs(fatJarURL.getPath(), "BOOT-INF/classes/"); assertEquals(1, urls.size()); urls.forEach(url -> assertEquals("sofa-ark-sample-springboot-ark", parseArtifactId(url.getPath()))); } private List extractResourceURLs(String pathToFatJar, String resourcePattern) { List resourceUrls = new ArrayList<>(); try (JarFile jarFile = new JarFile(new File(pathToFatJar))) { Enumeration entryEnumeration = jarFile.entries(); while (entryEnumeration.hasMoreElements()) { JarEntry entry = entryEnumeration.nextElement(); String entryName = entry.getName(); if (entryName.endsWith(resourcePattern)) { // 检查资源模式匹配 // 构建jar内资源的URL URL entryUrl = new URL("jar:file:" + pathToFatJar + "!/" + entryName); resourceUrls.add(entryUrl); } } } catch (IOException e) { throw new RuntimeException("Failed to extract resource URLs from " + pathToFatJar, e); } return resourceUrls; } @Test public void testParseArtifactIdFromJarName() throws Exception { Method method = JarUtils.class.getDeclaredMethod("doGetArtifactIdFromFileName", String.class); method.setAccessible(Boolean.TRUE); String filePathPrefix = "file:///home/admin/xxx/xxx/%s.jar"; String artifactId0 = (String) method.invoke(JarUtils.class, format(filePathPrefix, "dafdfa-2-dafdfad")); assertNull(artifactId0); String artifactId2 = (String) method.invoke(JarUtils.class, format(filePathPrefix, "dfadfa-dfadfa-3.0")); assertEquals(artifactId2, "dfadfa-dfadfa"); String artifactId3 = (String) method.invoke(JarUtils.class, format(filePathPrefix, "hessian-4.0.7.bugfix12-tuning3")); assertEquals(artifactId3, "hessian"); String artifactId4 = (String) method.invoke(JarUtils.class, format(filePathPrefix, "hessian-4.0.7")); assertEquals(artifactId4, "hessian"); } @Test public void testParseArtifactIdFromJarInJarName() throws Exception { Method method = JarUtils.class.getDeclaredMethod("doGetArtifactIdFromFileName", String.class); method.setAccessible(Boolean.TRUE); String filePathPrefix = "file:///home/admin/xxx/xxx/bootstrap-executable.jar!/META-INF/lib/%s.jar"; String artifactId0 = (String) method.invoke(JarUtils.class, format(filePathPrefix, "dafdfa-2-dafdfad")); assertNull(artifactId0); String artifactId1 = (String) method.invoke(JarUtils.class, format(filePathPrefix, "jar-2-version-suffix")); assertNull(artifactId1); String artifactId2 = (String) method.invoke(JarUtils.class, format(filePathPrefix, "dfadfa-dfadfa-3.0")); assertEquals(artifactId2, "dfadfa-dfadfa"); String artifactId3 = (String) method.invoke(JarUtils.class, format(filePathPrefix, "hessian-4.0.7.bugfix12-tuning3")); assertEquals(artifactId3, "hessian"); String artifactId4 = (String) method.invoke(JarUtils.class, format(filePathPrefix, "hessian-4.0.7")); assertEquals(artifactId4, "hessian"); } @Test public void testParseArtifactIdFromJarInJar() throws Exception { URL jar = JarUtilsTest.class.getResource("/sample-biz-withjar.jar"); Method method = JarUtils.class.getDeclaredMethod("parseArtifactIdFromJar", String.class); method.setAccessible(Boolean.TRUE); assertEquals("slf4j-api", method.invoke(JarUtils.class, jar.getFile() + "!/lib/slf4j-api-1.7.30.jar")); } @Test public void testParseArtifactIdFromJarInJarPom() { URL jar = JarUtilsTest.class.getResource("/sample-biz-withjar.jar"); String artifactId0 = parseArtifactId(jar.getFile() + "!/lib/slf4j-api-1.7.30.jar!/"); assertEquals("slf4j-api", artifactId0); } @Test public void testParseArtifactIdFromJarWithBlankPath() throws Exception { URL jar = JarUtilsTest.class.getResource("/junit-4.12.jar"); URL root = JarUtilsTest.class.getResource("/"); String fullPath = root.getPath() + "space directory"; String jarLocation = fullPath + "/junit-4.12.jar"; FileUtils.mkdir(fullPath); Files.copy(FileUtils.file(jar.getFile()), FileUtils.file(jarLocation)); URL url = JarUtilsTest.class.getResource("/space directory/junit-4.12.jar"); Method method = JarUtils.class.getDeclaredMethod("parseArtifactIdFromJar", String.class); method.setAccessible(Boolean.TRUE); assertNull(method.invoke(JarUtils.class, url.getPath())); } @Test public void testParseArtifactIdFromJarInJarInJarMore() { URL jar = JarUtilsTest.class.getResource("/example-jarinjarinjar.jar"); String artifactId0 = parseArtifactId(jar.getFile() + "!/BOOT-INF/lib/example-client-2.0.0.jar!/BOOT-INF/lib/sofa-ark-spring-guides-230525-SOFA.jar!/"); assertEquals("sofa-ark-spring-guides", artifactId0); String artifactId1 = parseArtifactId(jar.getFile() + "!/BOOT-INF/lib/example-client-2.0.0.jar!/BOOT-INF/lib/example-client-3.0.0.jar!/"); assertEquals("example-client", artifactId1); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/java/com/alipay/sofa/ark/loader/test/util/ModifyPathUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.loader.test.util; import com.alipay.sofa.ark.loader.util.ModifyPathUtils; import org.junit.Assert; import org.junit.Test; /** * @author sususama * @since 2023-05-08 */ public class ModifyPathUtilsTest { @Test public void modifyPathTest() { String path = "/C:/Users/XXX/Desktop/XXX-ark-biz.jar"; Assert.assertEquals("C:/Users/XXX/Desktop/XXX-ark-biz.jar", ModifyPathUtils.modifyPath(path)); String path1 = "C:/Users/XXX/Desktop/XXX-ark-biz.jar"; Assert.assertEquals("C:/Users/XXX/Desktop/XXX-ark-biz.jar", ModifyPathUtils.modifyPath(path1)); String path2 = "/home/user/XXX/test/XXX-ark-biz.jar"; Assert.assertEquals("/home/user/XXX/test/XXX-ark-biz.jar", ModifyPathUtils.modifyPath(path2)); } } ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/resources/conf/ark/bootstrap.properties ================================================ profile=default ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/resources/conf/ark/log/logback-conf.xml ================================================ ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/resources/empty-file ================================================ ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/resources/exploded-archive-test/META-INF/MANIFEST.MF ================================================ Manifest-Version: 1.0.0 ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/resources/pom-properties/pom.properties ================================================ a=b ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/resources/pom.xml ================================================ 4.0.0 sofa-ark-core-impl com.alipay.sofa ${sofa.ark.version} sofa-ark-archive com.alipay.sofa sofa-ark-spi com.alipay.sofa sofa-ark-api com.alipay.sofa sofa-ark-common commons-io commons-io org.apache.maven maven-model junit junit test org.mockito mockito-inline ${mockito.version} test org.springframework.boot spring-boot-loader test ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/resources/sample-springboot-fat-biz.jar ================================================ [File too large to display: 29.6 MB] ================================================ FILE: sofa-ark-parent/core-impl/archive/src/test/resources/xxxxx.jar-unpack/pom.properties ================================================ #Generated by Maven #Wed Jan 28 11:40:04 CST 2026 artifactId=xxxx-test groupId=com.xxxx.base version=0.0.1 ================================================ FILE: sofa-ark-parent/core-impl/container/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-container` **Package**: `com.alipay.sofa.ark.container` This is the core implementation of the Ark Container - the runtime that manages the entire SOFAArk lifecycle including plugins and business modules. ## Purpose - Ark container startup and shutdown - Manage plugins and business modules lifecycle - Provide classloader isolation - Service registry and dependency injection - Telnet command server for runtime management ## Key Classes ### Container Entry - `ArkContainer` - Main entry point, manages container lifecycle - `main(String[] args)` - Static entry for JAR execution - `start()` - Start the container - `stop()` - Stop the container ### Pipeline Stages (`pipeline/`) Startup stages executed in order: 1. `HandleArchiveStage` - Parse archives 2. `RegisterServiceStage` - Register core services 3. `ExtensionLoaderStage` - Load SPI extensions 4. `DeployPluginStage` - Start plugins 5. `DeployBizStage` - Start business modules 6. `FinishStartupStage` - Complete startup StandardPipeline.java:47-60 defines the startup sequence. ### Service Implementations (`service/`) - `ArkServiceContainer` - Guice-based service container - `biz.BizManagerServiceImpl` - Biz lifecycle management - `biz.BizFactoryServiceImpl` - Create Biz instances - `plugin.PluginManagerServiceImpl` - Plugin management - `plugin.PluginFactoryServiceImpl` - Create Plugin instances - `classloader.ClassLoaderServiceImpl` - Classloader management - `classloader.BizClassLoader` - Business module classloader - `classloader.PluginClassLoader` - Plugin classloader - `injection.InjectionServiceImpl` - Dependency injection - `event.EventAdminServiceImpl` - Event dispatching ### Session/Command (`session/`) - `StandardTelnetServerImpl` - Telnet server for runtime commands - `NettyTelnetServer` - Netty-based telnet server - Command handlers for: biz, plugin, info queries ## ClassLoader Architecture ``` Bootstrap ClassLoader ↓ Ark Container ClassLoader (loads sofa-ark-all) ↓ PluginClassLoader (bidirectional delegation between plugins) ↓ BizClassLoader (delegates to plugins, isolated from other biz) ``` ## Dependencies - `sofa-ark-spi` - Service interfaces - `sofa-ark-api` - API layer - `sofa-ark-archive` - Archive loading - `sofa-ark-common` - Utilities - `guice` - Dependency injection ## Used By - `sofa-ark-all` - Aggregates this module - Application at runtime (via `java -jar`) ================================================ FILE: sofa-ark-parent/core-impl/container/pom.xml ================================================ 4.0.0 sofa-ark-core-impl com.alipay.sofa ${sofa.ark.version} sofa-ark-container ${project.groupId}:${project.artifactId} com.alipay.sofa sofa-ark-spi com.alipay.sofa sofa-ark-api com.alipay.sofa sofa-ark-common com.alipay.sofa sofa-ark-exception com.alipay.sofa sofa-ark-archive com.google.inject guice io.netty netty-all com.taobao.text text-ui 0.0.3 net.bytebuddy byte-buddy-agent 1.14.4 org.mockito mockito-inline ${mockito.version} test junit junit test com.github.stefanbirkner system-rules test ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/ArkContainer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.bootstrap.ClasspathLauncher.ClassPathArchive; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.pipeline.DeployBizStage; import com.alipay.sofa.ark.container.pipeline.HandleArchiveStage; import com.alipay.sofa.ark.container.service.ArkServiceContainer; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.loader.EmbedClassPathArchive; import com.alipay.sofa.ark.loader.ExecutableArkBizJar; import com.alipay.sofa.ark.loader.archive.ExplodedArchive; import com.alipay.sofa.ark.loader.archive.JarFileArchive; import com.alipay.sofa.ark.spi.archive.BizArchive; import com.alipay.sofa.ark.spi.archive.ExecutableArchive; import com.alipay.sofa.ark.spi.argument.LaunchCommand; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.pipeline.Pipeline; import com.alipay.sofa.ark.spi.pipeline.PipelineContext; import com.alipay.sofa.ark.spi.service.biz.AddBizToStaticDeployHook; import com.alipay.sofa.ark.spi.service.extension.ArkServiceLoader; import com.alipay.sofa.common.log.MultiAppLoggerSpaceManager; import com.alipay.sofa.common.log.SpaceId; import com.alipay.sofa.common.log.SpaceInfo; import com.alipay.sofa.common.log.env.LogEnvUtils; import com.alipay.sofa.common.log.factory.LogbackLoggerSpaceFactory; import com.alipay.sofa.common.utils.ReportUtil; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import static com.alipay.sofa.ark.spi.constant.Constants.ARK_CONF_FILE; import static com.alipay.sofa.ark.spi.constant.Constants.ARK_CONF_FILE_FORMAT; import static com.alipay.sofa.common.log.Constants.LOGGING_PATH_DEFAULT; import static com.alipay.sofa.common.log.Constants.LOG_ENCODING_PROP_KEY; import static com.alipay.sofa.common.log.Constants.LOG_PATH; import static com.alipay.sofa.common.log.Constants.UTF8_STR; /** * Ark Container Entry * * @author ruoshan * @since 0.1.0 */ public class ArkContainer { private ArkServiceContainer arkServiceContainer; private PipelineContext pipelineContext; private AtomicBoolean started = new AtomicBoolean(false); private AtomicBoolean stopped = new AtomicBoolean(false); private long start = System.currentTimeMillis(); private List addBizToStaticDeployHooks; /** * -Aclasspath or -Ajar is needed at lease. it specify the abstract executable ark archive, * default added by container itself */ private static final int MINIMUM_ARGS_SIZE = 1; public static Object main(String[] args) throws ArkRuntimeException { if (args.length < MINIMUM_ARGS_SIZE) { throw new ArkRuntimeException("Please provide suitable arguments to continue !"); } try { LaunchCommand launchCommand = LaunchCommand.parse(args); if (launchCommand.isExecutedByCommandLine()) { ExecutableArkBizJar executableArchive; File rootFile = FileUtils.file(launchCommand.getExecutableArkBizJar().getFile()); if (rootFile.isDirectory()) { executableArchive = new ExecutableArkBizJar(new ExplodedArchive(rootFile)); } else { executableArchive = new ExecutableArkBizJar(new JarFileArchive(rootFile, launchCommand.getExecutableArkBizJar())); } return new ArkContainer(executableArchive, launchCommand).start(); } else { ClassPathArchive classPathArchive; if (ArkConfigs.isEmbedEnable()) { classPathArchive = new EmbedClassPathArchive(launchCommand.getEntryClassName(), launchCommand.getEntryMethodName(), launchCommand.getClasspath()); } else { classPathArchive = new ClassPathArchive(launchCommand.getEntryClassName(), launchCommand.getEntryMethodName(), launchCommand.getClasspath()); } return new ArkContainer(classPathArchive, launchCommand).start(); } } catch (IOException e) { throw new ArkRuntimeException(String.format("SOFAArk startup failed, commandline=%s", LaunchCommand.toString(args)), e); } } public ArkContainer(ExecutableArchive executableArchive) throws Exception { this(executableArchive, new LaunchCommand().setExecutableArkBizJar(executableArchive .getUrl())); } public ArkContainer(ExecutableArchive executableArchive, LaunchCommand launchCommand) { arkServiceContainer = new ArkServiceContainer(launchCommand.getLaunchArgs()); pipelineContext = new PipelineContext(); pipelineContext.setExecutableArchive(executableArchive); pipelineContext.setLaunchCommand(launchCommand); } /** * Start Ark Container * * @throws ArkRuntimeException * @since 0.1.0 */ public Object start() throws ArkRuntimeException { AssertUtils.assertNotNull(arkServiceContainer, "arkServiceContainer is null !"); if (started.compareAndSet(false, true)) { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override public void run() { stop(); } })); prepareArkConfig(); // don't remove this log print, add to init log space first before initialize ArkLogger ArkLoggerFactory.getDefaultLogger().info("Ark container starting..."); reInitializeArkLogger(); arkServiceContainer.start(); Pipeline pipeline = arkServiceContainer.getService(Pipeline.class); pipeline.process(pipelineContext); System.out.println("Ark container started in " + (System.currentTimeMillis() - start) //NOPMD + " ms."); } return this; } public Object deployBizAfterMasterBizReady() throws Exception { // Scan all biz in classpath HandleArchiveStage handleArchiveStage = ArkServiceContainerHolder.getContainer() .getService(HandleArchiveStage.class); handleArchiveStage.processStaticBizFromClasspath(pipelineContext); // execute beforeEmbedStaticDeployBizHook addStaticBizFromCustomHooks(); // start up DeployBizStage deployBizStage = ArkServiceContainerHolder.getContainer().getService( DeployBizStage.class); deployBizStage.processStaticBiz(pipelineContext); return this; } private void addStaticBizFromCustomHooks() throws Exception { addBizToStaticDeployHooks = ArkServiceLoader.loadExtensionsFromArkBiz( AddBizToStaticDeployHook.class, ArkClient.getMasterBiz().getIdentity()); for (AddBizToStaticDeployHook hook : addBizToStaticDeployHooks) { List bizsFromHook = hook.getStaticBizToAdd(); addStaticBiz(bizsFromHook); } } private void addStaticBiz(List bizArchives) throws IOException { if (null == bizArchives) { return; } for (BizArchive bizArchive : bizArchives) { Biz biz = ArkClient.getBizFactoryService().createBiz(bizArchive); ArkClient.getBizManagerService().registerBiz(biz); } } /** * Prepare to read ark conf * @throws ArkRuntimeException */ public void prepareArkConfig() throws ArkRuntimeException { try { // Forbid to Monitoring and Management Using JMX, because it leads to conflict when setup multi spring boot app. ArkConfigs.setSystemProperty(Constants.SPRING_BOOT_ENDPOINTS_JMX_ENABLED, String.valueOf(false)); // ignore thread class loader when loading classes and resource in log4j ArkConfigs.setSystemProperty(Constants.LOG4J_IGNORE_TCL, String.valueOf(true)); // compatible sofa-hessian4, refer to https://github.com/sofastack/sofa-hessian/issues/38 ArkConfigs.setSystemProperty(Constants.RESOLVE_PARENT_CONTEXT_SERIALIZER_FACTORY, "false"); // read ark conf file List urls = getProfileConfFiles(pipelineContext.getLaunchCommand().getProfiles()); ArkConfigs.init(urls); } catch (Throwable throwable) { throw new ArkRuntimeException(throwable); } } public List getProfileConfFiles(String... profiles) { List urls = new ArrayList<>(); for (String profile : profiles) { URL url; if (StringUtils.isEmpty(profile)) { url = this.getClass().getClassLoader().getResource(ARK_CONF_FILE); } else { url = this.getClass().getClassLoader() .getResource(String.format(ARK_CONF_FILE_FORMAT, profile)); } if (url != null) { urls.add(url); } else if (!StringUtils.isEmpty(profile)) { ReportUtil.reportWarn(String.format("The %s conf file is not found.", profile)); } } return urls; } /** * reInitialize Ark Logger * * @throws ArkRuntimeException */ public void reInitializeArkLogger() throws ArkRuntimeException { for (Map.Entry entry : ((Map) MultiAppLoggerSpaceManager .getSpacesMap()).entrySet()) { SpaceId spaceId = entry.getKey(); SpaceInfo spaceInfo = entry.getValue(); if (!ArkLoggerFactory.SOFA_ARK_LOGGER_SPACE.equals(spaceId.getSpaceName())) { continue; } LogbackLoggerSpaceFactory arkLoggerSpaceFactory = (LogbackLoggerSpaceFactory) spaceInfo .getAbstractLoggerSpaceFactory(); Map arkLogConfig = new HashMap<>(); // set base logging.path arkLogConfig.put(LOG_PATH, ArkConfigs.getStringValue(LOG_PATH, LOGGING_PATH_DEFAULT)); // set log file encoding arkLogConfig.put(LOG_ENCODING_PROP_KEY, ArkConfigs.getStringValue(LOG_ENCODING_PROP_KEY, UTF8_STR)); // set other log config for (String key : ArkConfigs.keySet()) { if (LogEnvUtils.filterAllLogConfig(key)) { arkLogConfig.put(key, ArkConfigs.getStringValue(key)); } } arkLoggerSpaceFactory.reInitialize(arkLogConfig); } } /** * Whether Ark Container is started or not * * @return */ public boolean isStarted() { return started.get(); } /** * Stop Ark Container * * @throws ArkRuntimeException */ public void stop() throws ArkRuntimeException { AssertUtils.assertNotNull(arkServiceContainer, "arkServiceContainer is null !"); if (stopped.compareAndSet(false, true)) { arkServiceContainer.stop(); } } /** * Whether Ark Container is running or not * @return */ public boolean isRunning() { return isStarted() && !stopped.get(); } /** * Get {@link ArkServiceContainer} of ark container * * @return */ public ArkServiceContainer getArkServiceContainer() { return arkServiceContainer; } /** * Get {@link PipelineContext} of ark container * * @return */ public PipelineContext getPipelineContext() { return pipelineContext; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/guice/ContainerModule.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.guice; import com.alipay.sofa.ark.common.guice.AbstractArkGuiceModule; import com.alipay.sofa.ark.container.service.biz.BizDeployServiceImpl; import com.alipay.sofa.ark.container.service.biz.BizFactoryServiceImpl; import com.alipay.sofa.ark.container.service.biz.BizManagerServiceImpl; import com.alipay.sofa.ark.container.service.event.EventAdminServiceImpl; import com.alipay.sofa.ark.container.service.extension.ExtensionLoaderServiceImpl; import com.alipay.sofa.ark.container.service.injection.InjectionServiceImpl; import com.alipay.sofa.ark.container.service.plugin.PluginFactoryServiceImpl; import com.alipay.sofa.ark.container.service.plugin.PluginManagerServiceImpl; import com.alipay.sofa.ark.container.pipeline.StandardPipeline; import com.alipay.sofa.ark.container.service.classloader.ClassLoaderServiceImpl; import com.alipay.sofa.ark.container.service.plugin.PluginDeployServiceImpl; import com.alipay.sofa.ark.container.service.registry.RegistryServiceImpl; import com.alipay.sofa.ark.container.session.StandardTelnetServerImpl; import com.alipay.sofa.ark.spi.service.biz.BizDeployService; import com.alipay.sofa.ark.spi.service.biz.BizFactoryService; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import com.alipay.sofa.ark.spi.service.extension.ExtensionLoaderService; import com.alipay.sofa.ark.spi.service.injection.InjectionService; import com.alipay.sofa.ark.spi.service.plugin.PluginFactoryService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import com.alipay.sofa.ark.spi.pipeline.Pipeline; import com.alipay.sofa.ark.spi.service.ArkService; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderService; import com.alipay.sofa.ark.spi.service.plugin.PluginDeployService; import com.alipay.sofa.ark.spi.service.registry.RegistryService; import com.alipay.sofa.ark.spi.service.session.TelnetServerService; import com.google.inject.multibindings.Multibinder; /** * Guice module for ark container * * @author ruoshan * @since 0.1.0 */ public class ContainerModule extends AbstractArkGuiceModule { @Override protected void configure() { binder().bind(Pipeline.class).to(StandardPipeline.class); Multibinder arkServiceMultibinder = Multibinder.newSetBinder(binder(), ArkService.class); arkServiceMultibinder.addBinding().to(PluginDeployServiceImpl.class); arkServiceMultibinder.addBinding().to(BizDeployServiceImpl.class); arkServiceMultibinder.addBinding().to(ClassLoaderServiceImpl.class); arkServiceMultibinder.addBinding().to(StandardTelnetServerImpl.class); binder().bind(PluginManagerService.class).to(PluginManagerServiceImpl.class); binder().bind(BizManagerService.class).to(BizManagerServiceImpl.class); binder().bind(ClassLoaderService.class).to(ClassLoaderServiceImpl.class); binder().bind(PluginDeployService.class).to(PluginDeployServiceImpl.class); binder().bind(BizDeployService.class).to(BizDeployServiceImpl.class); binder().bind(RegistryService.class).to(RegistryServiceImpl.class); binder().bind(InjectionService.class).to(InjectionServiceImpl.class); binder().bind(TelnetServerService.class).to(StandardTelnetServerImpl.class); binder().bind(BizFactoryService.class).to(BizFactoryServiceImpl.class); binder().bind(PluginFactoryService.class).to(PluginFactoryServiceImpl.class); binder().bind(ExtensionLoaderService.class).to(ExtensionLoaderServiceImpl.class); binder().bind(EventAdminService.class).to(EventAdminServiceImpl.class); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/model/BizModel.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.model; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.bootstrap.MainMethodRunner; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.BizIdentityUtils; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.common.util.ParseUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.container.service.classloader.AbstractClasspathClassLoader; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.loader.jar.JarUtils; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.biz.AfterBizStartupEvent; import com.alipay.sofa.ark.spi.event.biz.AfterBizStartupFailedEvent; import com.alipay.sofa.ark.spi.event.biz.AfterBizStopEvent; import com.alipay.sofa.ark.spi.event.biz.AfterBizStopFailedEvent; import com.alipay.sofa.ark.spi.event.biz.BeforeBizRecycleEvent; import com.alipay.sofa.ark.spi.event.biz.BeforeBizStartupEvent; import com.alipay.sofa.ark.spi.event.biz.BeforeBizStopEvent; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.ReentrantLock; import static com.alipay.sofa.ark.spi.constant.Constants.BIZ_TEMP_WORK_DIR_RECYCLE_FILE_SUFFIX; import static com.alipay.sofa.ark.spi.constant.Constants.ACTIVATE_MULTI_BIZ_VERSION_ENABLE; import static com.alipay.sofa.ark.spi.constant.Constants.REMOVE_BIZ_INSTANCE_AFTER_STOP_FAILED; import static org.apache.commons.io.FileUtils.deleteQuietly; /** * Ark Biz Standard Model * * @author ruoshan * @since 0.1.0 */ public class BizModel implements Biz { private String bizName; private String bizVersion; private BizState bizState; private String mainClass; private String webContextPath; private URL[] urls; private URL bizUrl; private URL[] pluginUrls; private ClassLoader classLoader; private Map attributes = new ConcurrentHashMap<>(); private int priority = DEFAULT_PRECEDENCE; private Set denyImportPackages; private Set denyImportPackageNodes = new HashSet<>(); private Set denyImportPackageStems = new HashSet<>(); private Set denyImportClasses; private Set denyImportResources = new HashSet<>(); private Set injectPluginDependencies = new HashSet<>(); private Set injectExportPackages = new HashSet<>(); private Set declaredLibraries = new LinkedHashSet<>(); private Map declaredCacheMap = new ConcurrentHashMap<>(); private Set denyPrefixImportResourceStems = new HashSet<>(); private Set denySuffixImportResourceStems = new HashSet<>(); private File bizTempWorkDir; private List bizStateRecords = new CopyOnWriteArrayList<>(); private Set dependentPlugins = new HashSet<>(); public BizModel setBizName(String bizName) { AssertUtils.isFalse(StringUtils.isEmpty(bizName), "Biz Name must not be empty!"); this.bizName = bizName; return this; } public BizModel setBizVersion(String bizVersion) { AssertUtils.isFalse(StringUtils.isEmpty(bizVersion), "Biz Version must not be empty!"); this.bizVersion = bizVersion; return this; } public BizModel setBizState(BizState bizState) { this.bizState = bizState; addStateChangeLog(StateChangeReason.UNDEFINE, ""); return this; } public BizModel setBizState(BizState bizState, StateChangeReason reason) { this.bizState = bizState; addStateChangeLog(reason, ""); return this; } public BizModel setBizState(BizState bizState, StateChangeReason reason, String message) { this.bizState = bizState; addStateChangeLog(reason, message); return this; } public BizModel setMainClass(String mainClass) { AssertUtils.isFalse(StringUtils.isEmpty(mainClass), "Biz Main Class must not be empty!"); this.mainClass = mainClass; return this; } public BizModel setClassPath(URL[] urls) { this.urls = urls; return this; } public BizModel setBizUrl(URL url) { this.bizUrl = url; return this; } public BizModel setPluginClassPath(URL[] urls) { this.pluginUrls = urls; return this; } public BizModel setClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; return this; } public BizModel setPriority(String priority) { this.priority = (priority == null ? DEFAULT_PRECEDENCE : Integer.valueOf(priority)); return this; } public BizModel setWebContextPath(String webContextPath) { this.webContextPath = (webContextPath == null ? Constants.ROOT_WEB_CONTEXT_PATH : webContextPath); return this; } public BizModel setDenyImportPackages(String denyImportPackages) { this.denyImportPackages = StringUtils.strToSet(denyImportPackages, Constants.MANIFEST_VALUE_SPLIT); ParseUtils.parsePackageNodeAndStem(this.denyImportPackages, this.denyImportPackageStems, this.denyImportPackageNodes); return this; } public BizModel setDenyImportClasses(String denyImportClasses) { this.denyImportClasses = StringUtils.strToSet(denyImportClasses, Constants.MANIFEST_VALUE_SPLIT); return this; } public BizModel setDenyImportResources(String denyImportResources) { ParseUtils.parseResourceAndStem( StringUtils.strToSet(denyImportResources, Constants.MANIFEST_VALUE_SPLIT), this.denyPrefixImportResourceStems, denySuffixImportResourceStems, this.denyImportResources); return this; } public BizModel setAttribute(String key, String val) { attributes.put(key, val); return this; } public BizModel setAttributes(Map attributes) { this.attributes.putAll(attributes); return this; } public BizModel setInjectPluginDependencies(Set injectPluginDependencies) { this.injectPluginDependencies = injectPluginDependencies; return this; } public BizModel setInjectExportPackages(String injectExportPackages) { this.injectExportPackages = StringUtils.strToSet(injectExportPackages, Constants.MANIFEST_VALUE_SPLIT); return this; } public Set getInjectExportPackages() { return injectExportPackages; } private void addStateChangeLog(StateChangeReason reason, String message) { bizStateRecords.add(new BizStateRecord(new Date(), bizState, reason, message)); } public Set getDependentPlugins() { return dependentPlugins; } public BizModel setDependentPlugins(Set dependentPlugins) { this.dependentPlugins = dependentPlugins; return this; } @Override public String getBizName() { return bizName; } @Override public String getBizVersion() { return bizVersion; } @Override public String getIdentity() { return BizIdentityUtils.generateBizIdentity(this); } @Override public String getMainClass() { return mainClass; } @Override public URL[] getClassPath() { return urls; } @Override public URL getBizUrl() { return bizUrl; } @Override public int getPriority() { return priority; } @Override public ClassLoader getBizClassLoader() { return classLoader; } @Override public Set getDenyImportPackages() { return denyImportPackages; } @Override public Set getDenyImportPackageNodes() { return denyImportPackageNodes; } @Override public Set getDenyImportPackageStems() { return denyImportPackageStems; } @Override public Set getDenyImportClasses() { return denyImportClasses; } @Override public Set getDenyImportResources() { return denyImportResources; } public Set getInjectPluginDependencies() { return injectPluginDependencies; } @Override public Set getDenyPrefixImportResourceStems() { return denyPrefixImportResourceStems; } @Override public Set getDenySuffixImportResourceStems() { return denySuffixImportResourceStems; } @Override public void start(String[] args) throws Throwable { doStart(args, null); } @Override public void start(String[] args, Map envs) throws Throwable { doStart(args, envs); } private void doStart(String[] args, Map envs) throws Throwable { AssertUtils.isTrue(bizState == BizState.RESOLVED, "BizState must be RESOLVED"); // support specify mainClass by env if (envs != null) { String mainClassFromEnv = envs.get(Constants.BIZ_MAIN_CLASS); if (mainClassFromEnv != null) { mainClass = mainClassFromEnv; ArkLoggerFactory.getDefaultLogger().info( "Ark biz {} will start with main class {} from envs", getIdentity(), mainClassFromEnv); } } if (mainClass == null) { throw new ArkRuntimeException(String.format("biz: %s has no main method", getBizName())); } ClassLoader oldClassLoader = ClassLoaderUtils.pushContextClassLoader(this.classLoader); EventAdminService eventAdminService = ArkServiceContainerHolder.getContainer().getService( EventAdminService.class); try { eventAdminService.sendEvent(new BeforeBizStartupEvent(this)); resetProperties(); if (!isMasterBizAndEmbedEnable()) { long start = System.currentTimeMillis(); ArkLoggerFactory.getDefaultLogger().info("Ark biz {} start.", getIdentity()); MainMethodRunner mainMethodRunner = new MainMethodRunner(mainClass, args, envs); mainMethodRunner.run(); // this can trigger health checker handler eventAdminService.sendEvent(new AfterBizStartupEvent(this)); ArkLoggerFactory.getDefaultLogger().info("Ark biz {} started in {} ms", getIdentity(), (System.currentTimeMillis() - start)); } } catch (Throwable e) { setBizState(BizState.BROKEN, StateChangeReason.INSTALL_FAILED, getStackTraceAsString(e)); eventAdminService.sendEvent(new AfterBizStartupFailedEvent(this, e)); throw e; } finally { ClassLoaderUtils.popContextClassLoader(oldClassLoader); } BizManagerService bizManagerService = ArkServiceContainerHolder.getContainer().getService( BizManagerService.class); ReentrantLock bizLock = bizManagerService.getBizLock(bizName); // lock the bizName to ensure biz with same name install in sequence bizLock.lock(); try { // case0: active the first module as activated if (bizManagerService.getActiveBiz(bizName) == null) { setBizState(BizState.ACTIVATED, StateChangeReason.STARTED); return; } // case1: support multiple version biz as activated: always activate the new version and keep the old module activated boolean activateMultiBizVersion = Boolean.parseBoolean(ArkConfigs.getStringValue( ACTIVATE_MULTI_BIZ_VERSION_ENABLE, "false")); if (activateMultiBizVersion) { setBizState(BizState.ACTIVATED, StateChangeReason.STARTED); return; } // case2: always activate the new version and deactivate the old module according to ACTIVATE_NEW_MODULE config if (Boolean.getBoolean(Constants.ACTIVATE_NEW_MODULE)) { Biz currentActiveBiz = bizManagerService.getActiveBiz(bizName); ((BizModel) currentActiveBiz).setBizState(BizState.DEACTIVATED, StateChangeReason.SWITCHED, String.format("switch to new biz %s", getIdentity())); setBizState(BizState.ACTIVATED, StateChangeReason.STARTED, String.format("switch from old biz: %s", currentActiveBiz.getIdentity())); } else { // case3: always deactivate the new version and keep old module activated according to ACTIVATE_NEW_MODULE config setBizState(BizState.DEACTIVATED, StateChangeReason.STARTED, "start but is deactivated"); } } finally { // ensure the lock will be released, avoid deadlock bizLock.unlock(); } } @Override public void stop() { AssertUtils.isTrue(bizState == BizState.ACTIVATED || bizState == BizState.DEACTIVATED || bizState == BizState.BROKEN, "BizState must be ACTIVATED, DEACTIVATED or BROKEN."); if (isMasterBizAndEmbedEnable()) { // skip stop when embed mode return; } ClassLoader oldClassLoader = ClassLoaderUtils.pushContextClassLoader(this.classLoader); if (bizState == BizState.ACTIVATED) { setBizState(BizState.DEACTIVATED, StateChangeReason.KILLING); } EventAdminService eventAdminService = ArkServiceContainerHolder.getContainer().getService( EventAdminService.class); boolean isStopFailed = false; long start = System.currentTimeMillis(); try { // this can trigger uninstall handler ArkLoggerFactory.getDefaultLogger().info("Ark biz {} stops.", getIdentity()); eventAdminService.sendEvent(new BeforeBizStopEvent(this)); ArkLoggerFactory.getDefaultLogger().info("Ark biz {} stopped in {} ms", getIdentity(), (System.currentTimeMillis() - start)); } catch (Throwable t) { // handle stop failed ArkLoggerFactory.getDefaultLogger().info("Ark biz {} stop failed in {} ms", getIdentity(), (System.currentTimeMillis() - start)); isStopFailed = true; setBizState(BizState.BROKEN, StateChangeReason.UN_INSTALL_FAILED); eventAdminService.sendEvent(new AfterBizStopFailedEvent(this, t)); throw t; } finally { boolean removeInstanceAfterStopFailed = Boolean.parseBoolean(ArkConfigs.getStringValue( REMOVE_BIZ_INSTANCE_AFTER_STOP_FAILED, "true")); // 只有成功后才清理, 或者失败后允许清理的情况,失败后如果不允许情况则不执行清理 if (!isStopFailed || (isStopFailed && removeInstanceAfterStopFailed)) { BizManagerService bizManagerService = ArkServiceContainerHolder.getContainer() .getService(BizManagerService.class); bizManagerService.unRegisterBiz(bizName, bizVersion); setBizState(BizState.STOPPED, StateChangeReason.STOPPED); eventAdminService.sendEvent(new BeforeBizRecycleEvent(this)); urls = null; denyImportPackages = null; denyImportClasses = null; denyImportResources = null; // close classloader if (classLoader instanceof AbstractClasspathClassLoader) { try { ((AbstractClasspathClassLoader) classLoader).close(); ((AbstractClasspathClassLoader) classLoader).clearCache(); } catch (IOException e) { ArkLoggerFactory.getDefaultLogger().warn( "Ark biz {} close biz classloader fail", getIdentity()); } } eventAdminService.sendEvent(new AfterBizStopEvent(this)); eventAdminService.unRegister(classLoader); classLoader = null; recycleBizTempWorkDir(bizTempWorkDir); bizTempWorkDir = null; } ClassLoaderUtils.popContextClassLoader(oldClassLoader); } } @Override public void setCustomBizName(String bizName) { this.bizName = bizName; } @Override public BizState getBizState() { return bizState; } @Override public String getWebContextPath() { return webContextPath; } @Override public Map getAttributes() { return attributes; } @Override public List getBizStateRecords() { return bizStateRecords; } @Override public String toString() { return "Ark Biz: " + getIdentity() + ",\n biz url: " + bizUrl + ",\n classloader: " + classLoader + ",\n current state: " + bizState + ",\n history states: " + bizStateRecords; } private void resetProperties() { if (!ArkConfigs.isEmbedEnable()) { System.getProperties().remove("logging.path"); } else if (this != ArkClient.getMasterBiz()) { System.getProperties().remove("spring.application.admin.enabled"); } } public File getBizTempWorkDir() { return bizTempWorkDir; } public BizModel setBizTempWorkDir(File bizTempWorkDir) { this.bizTempWorkDir = bizTempWorkDir; return this; } private boolean isMasterBizAndEmbedEnable() { return this == ArkClient.getMasterBiz() && ArkConfigs.isEmbedEnable(); } public BizModel setDeclaredLibraries(String declaredLibraries) { if (StringUtils.isEmpty(declaredLibraries)) { return this; } this.declaredLibraries = StringUtils.strToSet(declaredLibraries, Constants.MANIFEST_VALUE_SPLIT); return this; } /** * check if the resource is defined in classloader, ignore jar version * @param url * @return */ public boolean isDeclared(URL url, String resourceName) { // compatibility with no-declaredMode if (!isDeclaredMode()) { return true; } if (url != null) { String libraryFile = url.getFile().replace("file:", ""); if (!StringUtils.isEmpty(resourceName) && libraryFile.endsWith(resourceName)) { libraryFile = libraryFile.substring(0, libraryFile.lastIndexOf(resourceName)); } return checkDeclaredWithCache(libraryFile); } return false; } public boolean isDeclaredMode() { if (declaredLibraries == null || declaredLibraries.size() == 0) { return false; } return true; } private boolean checkDeclaredWithCache(String libraryFile) { // set key as jar, but need to checkDeclared by specific file. return declaredCacheMap.computeIfAbsent(libraryFile, this::doCheckDeclared); } boolean doCheckDeclared(String jarFilePath) { // if from ark plugin, then set as declared if (isFromPlugin(jarFilePath)) { return true; } String artifactId = JarUtils.parseArtifactId(jarFilePath); if (artifactId == null) { if (jarFilePath.contains(".jar!") || jarFilePath.endsWith(".jar")) { // if in jar, and can't get artifactId from jar file, then just rollback to all delegate. ArkLoggerFactory.getDefaultLogger().info( String.format("Can't find artifact id for %s, default as declared.", jarFilePath)); return true; } else { // for not in jar, then default not delegate. ArkLoggerFactory.getDefaultLogger().info( String.format("Can't find artifact id for %s, default as not declared.", jarFilePath)); return false; } } // some ark related lib which each ark module needed should set declared as default if (StringUtils.startWithToLowerCase(artifactId, "sofa-ark-")) { return true; } return declaredLibraries.contains(artifactId); } private boolean isFromPlugin(String jarFilePath) { if (pluginUrls == null) { return false; } for (URL pluginUrl : pluginUrls) { String pluginUrlFile = pluginUrl.getFile().replace("file:", ""); if (pluginUrlFile.endsWith(JarUtils.JAR_SEPARATOR)) { pluginUrlFile = pluginUrlFile.substring(0, pluginUrlFile.length() - JarUtils.JAR_SEPARATOR.length()); } if (jarFilePath.contains(pluginUrlFile)) { return true; } } return false; } /** * recycle biz temp work dir * * @param bizTempWorkDir * @return */ public static boolean recycleBizTempWorkDir(File bizTempWorkDir) { if (bizTempWorkDir == null) { return false; } if (bizTempWorkDir.isDirectory()) { try { String newPath = markBizTempWorkDirRecycled(bizTempWorkDir); File markedFile = new File(newPath); if (!markedFile.exists()) { ArkLoggerFactory.getDefaultLogger().warn( String.format("when delete marked biz temp work dir %s, file not exists ", markedFile.getPath())); return false; } return deleteQuietly(markedFile); } catch (IOException e) { throw new ArkRuntimeException("mark and delete biz temp work dir error: " + e.getMessage()); } } return deleteQuietly(bizTempWorkDir); } /** * mark biz temp work dir is recycled * * @param bizTempWorkDir * @return * @throws IOException */ private static String markBizTempWorkDirRecycled(File bizTempWorkDir) throws IOException { String sourcePath = bizTempWorkDir.getAbsolutePath(); String targetPath = String.format("%s-%s-%s", sourcePath, System.currentTimeMillis(), BIZ_TEMP_WORK_DIR_RECYCLE_FILE_SUFFIX); Files.move(Paths.get(sourcePath), Paths.get(targetPath)); ArkLoggerFactory.getDefaultLogger().info( String.format("move biz temp work dir from %s to %s", sourcePath, targetPath)); return targetPath; } private static String getStackTraceAsString(Throwable throwable) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); throwable.printStackTrace(pw); return sw.toString(); } /* export class and classloader relationship cache */ private ConcurrentHashMap exportClassAndClassLoaderMap = new ConcurrentHashMap<>(); private ConcurrentHashMap exportNodeAndClassLoaderMap = new ConcurrentHashMap<>(); private ConcurrentHashMap exportStemAndClassLoaderMap = new ConcurrentHashMap<>(); /* export cache and classloader relationship cache */ private ConcurrentHashMap> exportResourceAndClassLoaderMap = new ConcurrentHashMap<>(); private ConcurrentHashMap> exportPrefixStemResourceAndClassLoaderMap = new ConcurrentHashMap<>(); private ConcurrentHashMap> exportSuffixStemResourceAndClassLoaderMap = new ConcurrentHashMap<>(); public ConcurrentHashMap getExportClassAndClassLoaderMap() { return exportClassAndClassLoaderMap; } public ConcurrentHashMap getExportNodeAndClassLoaderMap() { return exportNodeAndClassLoaderMap; } public ConcurrentHashMap getExportStemAndClassLoaderMap() { return exportStemAndClassLoaderMap; } public ConcurrentHashMap> getExportResourceAndClassLoaderMap() { return exportResourceAndClassLoaderMap; } public ConcurrentHashMap> getExportPrefixStemResourceAndClassLoaderMap() { return exportPrefixStemResourceAndClassLoaderMap; } public ConcurrentHashMap> getExportSuffixStemResourceAndClassLoaderMap() { return exportSuffixStemResourceAndClassLoaderMap; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/model/PluginContextImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.model; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.registry.PluginServiceProvider; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.model.PluginContext; import com.alipay.sofa.ark.spi.registry.ServiceFilter; import com.alipay.sofa.ark.spi.registry.ServiceReference; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import com.alipay.sofa.ark.spi.service.registry.RegistryService; import java.util.List; import java.util.Set; /** * Standard Plugin Context Implement * * @author ruoshan * @since 0.1.0 */ public class PluginContextImpl implements PluginContext { private Plugin plugin; private PluginManagerService pluginManagerService = ArkServiceContainerHolder.getContainer() .getService(PluginManagerService.class); private RegistryService registryService = ArkServiceContainerHolder.getContainer() .getService(RegistryService.class); public PluginContextImpl(Plugin plugin) { this.plugin = plugin; } @Override public Plugin getPlugin() { return plugin; } @Override public Plugin getPlugin(String pluginName) { return pluginManagerService.getPluginByName(pluginName); } @Override public ServiceReference publishService(Class ifClass, T implObject) { return publishService(ifClass, implObject, StringUtils.EMPTY_STRING); } @Override public ServiceReference publishService(Class ifClass, T implObject, String uniqueId) { return registryService.publishService(ifClass, implObject, uniqueId, new PluginServiceProvider(plugin)); } @Override public ServiceReference referenceService(Class ifClass) { return referenceService(ifClass, StringUtils.EMPTY_STRING); } @Override @SuppressWarnings("unchecked") public ServiceReference referenceService(Class ifClass, String uniqueId) { return registryService.referenceService(ifClass, uniqueId); } @Override @SuppressWarnings("unchecked") public List referenceServices(ServiceFilter serviceFilter) { return registryService.referenceServices(serviceFilter); } @Override public Set getPluginNames() { return pluginManagerService.getAllPluginNames(); } @Override public ClassLoader getClassLoader() { return plugin.getPluginClassLoader(); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/model/PluginModel.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.model; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.common.util.ParseUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.plugin.AfterPluginStartupEvent; import com.alipay.sofa.ark.spi.event.plugin.AfterPluginStopEvent; import com.alipay.sofa.ark.spi.event.plugin.BeforePluginStartupEvent; import com.alipay.sofa.ark.spi.event.plugin.BeforePluginStopEvent; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.model.PluginContext; import com.alipay.sofa.ark.spi.service.PluginActivator; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import java.net.URL; import java.util.HashSet; import java.util.Set; /** * ARk Plugin Standard Model * * @author qilong.zql * @since 0.1.0 */ public class PluginModel implements Plugin { private String pluginName; private String groupId; private String artifactId; private String version; private int priority = DEFAULT_PRECEDENCE; /** * 0. default as 'classLoader' for load those classes in plugin classLoader * 1. 'override' for load those classes only as files, and those will be reload in other classLoaders. */ public static final String EXPORTMODE_CLASSLOADER = "classLoader"; public static final String EXPORTMODE_OVERRIDE = "override"; public static final String EXPORTMODE_UNKNOWN = "unknown"; private String exportMode = EXPORTMODE_CLASSLOADER; private Set exportPackages; private Set exportPackageNodes = new HashSet<>(); private Set exportPackageStems = new HashSet<>(); private Set exportClasses; private Set importPackages; private Set importPackageNodes = new HashSet<>(); private Set importPackageStems = new HashSet<>(); private Set importClasses; private Set importResources = new HashSet<>(); private Set importPrefixResourceStems = new HashSet<>(); private Set importSuffixResourceStems = new HashSet<>(); private Set exportResources = new HashSet<>(); private Set exportPrefixResourceStems = new HashSet<>(); private Set exportSuffixResourceStems = new HashSet<>(); private String activator; private URL[] urls; private URL pluginUrl; private ClassLoader pluginClassLoader; private PluginContext pluginContext; private PluginActivator pluginActivator; public PluginModel setPluginName(String pluginName) { this.pluginName = pluginName; return this; } public PluginModel setGroupId(String groupId) { this.groupId = groupId; return this; } public PluginModel setArtifactId(String artifactId) { this.artifactId = artifactId; return this; } public PluginModel setVersion(String version) { this.version = version; return this; } public PluginModel setPriority(String priority) { this.priority = (priority == null ? DEFAULT_PRECEDENCE : Integer.valueOf(priority)); return this; } public PluginModel setPluginActivator(String activator) { this.activator = activator; return this; } public PluginModel setClassPath(URL[] urls) { this.urls = urls; return this; } public PluginModel setExportMode(String exportMode) { this.exportMode = exportMode; return this; } public PluginModel setExportPackages(String exportPackages) { this.exportPackages = StringUtils.strToSet(exportPackages, Constants.MANIFEST_VALUE_SPLIT); ParseUtils.parsePackageNodeAndStem(this.exportPackages, this.exportPackageStems, this.exportPackageNodes); return this; } public PluginModel setExportPackages(String exportPackages, Set exportExtensionPackages) { this.exportPackages = StringUtils.strToSet(exportPackages, Constants.MANIFEST_VALUE_SPLIT); this.exportPackages.addAll(exportExtensionPackages); ParseUtils.parsePackageNodeAndStem(this.exportPackages, this.exportPackageStems, this.exportPackageNodes); return this; } public PluginModel setExportClasses(String exportClasses) { this.exportClasses = StringUtils.strToSet(exportClasses, Constants.MANIFEST_VALUE_SPLIT); return this; } public PluginModel setImportPackages(String importPackages) { this.importPackages = StringUtils.strToSet(importPackages, Constants.MANIFEST_VALUE_SPLIT); ParseUtils.parsePackageNodeAndStem(this.importPackages, this.importPackageStems, this.importPackageNodes); return this; } public PluginModel setImportClasses(String importClasses) { this.importClasses = StringUtils.strToSet(importClasses, Constants.MANIFEST_VALUE_SPLIT); return this; } public PluginModel setImportResources(String importResources) { ParseUtils.parseResourceAndStem( StringUtils.strToSet(importResources, Constants.MANIFEST_VALUE_SPLIT), this.importPrefixResourceStems, importSuffixResourceStems, this.importResources); return this; } public PluginModel setExportResources(String exportResources) { ParseUtils.parseResourceAndStem( StringUtils.strToSet(exportResources, Constants.MANIFEST_VALUE_SPLIT), this.exportPrefixResourceStems, exportSuffixResourceStems, this.exportResources); return this; } public PluginModel setPluginClassLoader(ClassLoader classLoader) { this.pluginClassLoader = classLoader; return this; } public PluginModel setPluginContext(PluginContext context) { this.pluginContext = context; return this; } public PluginModel setPluginUrl(URL pluginUrl) { this.pluginUrl = pluginUrl; return this; } @Override public int getPriority() { return this.priority; } @Override public String getPluginName() { return this.pluginName; } @Override public String getGroupId() { return this.groupId; } @Override public String getArtifactId() { return this.artifactId; } @Override public String getVersion() { return this.version; } @Override public String getPluginActivator() { return this.activator; } @Override public URL[] getClassPath() { return this.urls; } @Override public ClassLoader getPluginClassLoader() { return this.pluginClassLoader; } @Override public PluginContext getPluginContext() { return this.pluginContext; } @Override public String getExportMode() { if (StringUtils.isEmpty(this.exportMode)) { return EXPORTMODE_CLASSLOADER; } return this.exportMode; } @Override public Set getExportPackages() { return this.exportPackages; } @Override public Set getExportPackageNodes() { return exportPackageNodes; } @Override public Set getExportPackageStems() { return exportPackageStems; } @Override public Set getExportClasses() { return this.exportClasses; } @Override public Set getImportPackages() { return this.importPackages; } @Override public Set getImportPackageNodes() { return this.importPackageNodes; } @Override public Set getImportPackageStems() { return this.importPackageStems; } @Override public Set getImportClasses() { return this.importClasses; } @Override public Set getImportResources() { return importResources; } @Override public Set getImportPrefixResourceStems() { return importPrefixResourceStems; } @Override public Set getImportSuffixResourceStems() { return importSuffixResourceStems; } @Override public Set getExportResources() { return exportResources; } @Override public Set getExportPrefixResourceStems() { return exportPrefixResourceStems; } @Override public Set getExportSuffixResourceStems() { return exportSuffixResourceStems; } @Override public URL getPluginURL() { return pluginUrl; } @Override public void start() throws ArkRuntimeException { if (activator == null || activator.isEmpty()) { return; } EventAdminService eventAdminService = ArkServiceContainerHolder.getContainer().getService( EventAdminService.class); ClassLoader oldClassLoader = ClassLoaderUtils .pushContextClassLoader(this.pluginClassLoader); try { eventAdminService.sendEvent(new BeforePluginStartupEvent(this)); pluginActivator = (PluginActivator) pluginClassLoader.loadClass(activator) .newInstance(); pluginActivator.start(pluginContext); } catch (Throwable ex) { throw new ArkRuntimeException(ex.getMessage(), ex); } finally { eventAdminService.sendEvent(new AfterPluginStartupEvent(this)); ClassLoaderUtils.popContextClassLoader(oldClassLoader); } } @Override public void stop() throws ArkRuntimeException { EventAdminService eventAdminService = ArkServiceContainerHolder.getContainer().getService( EventAdminService.class); eventAdminService.sendEvent(new BeforePluginStopEvent(this)); try { if (pluginActivator != null) { pluginActivator.stop(pluginContext); } } catch (Throwable ex) { throw new ArkRuntimeException(ex.getMessage(), ex); } finally { eventAdminService.sendEvent(new AfterPluginStopEvent(this)); if (this.getPluginClassLoader() != null) { eventAdminService.unRegister(this.getPluginClassLoader()); } } } @Override public String toString() { return "Ark Plugin: " + pluginName; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/pipeline/DeployBizStage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.pipeline; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.event.AfterFinishDeployEvent; import com.alipay.sofa.ark.spi.pipeline.PipelineContext; import com.alipay.sofa.ark.spi.pipeline.PipelineStage; import com.alipay.sofa.ark.spi.service.biz.BizDeployService; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import com.google.inject.Inject; import com.google.inject.Singleton; /** * Pipeline Stage to Deploy Biz * * @author ruoshan * @since 0.1.0 */ @Singleton public class DeployBizStage implements PipelineStage { @Inject private BizDeployService bizDeployService; @Inject private EventAdminService eventAdminService; @Override public void process(PipelineContext pipelineContext) throws ArkRuntimeException { String[] args = pipelineContext.getLaunchCommand().getLaunchArgs(); bizDeployService.deploy(args); if (ArkConfigs.isEmbedEnable()) { return; } eventAdminService.sendEvent(new AfterFinishDeployEvent()); } public void processStaticBiz(PipelineContext pipelineContext) throws ArkRuntimeException { String[] args = pipelineContext.getLaunchCommand().getLaunchArgs(); bizDeployService.deploy(args); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/pipeline/DeployPluginStage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.pipeline; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.pipeline.PipelineContext; import com.alipay.sofa.ark.spi.pipeline.PipelineStage; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderService; import com.alipay.sofa.ark.spi.service.plugin.PluginDeployService; import com.google.inject.Inject; import com.google.inject.Singleton; /** * Deploy Plugin Stage * * @author ruoshan * @since 0.1.0 */ @Singleton public class DeployPluginStage implements PipelineStage { @Inject private ClassLoaderService classloaderService; @Inject private PluginDeployService pluginDeployService; @Override public void process(PipelineContext pipelineContext) throws ArkRuntimeException { classloaderService.prepareExportClassAndResourceCache(); pluginDeployService.deploy(); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/pipeline/ExtensionLoaderStage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.pipeline; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.pipeline.PipelineContext; import com.alipay.sofa.ark.spi.pipeline.PipelineStage; import com.alipay.sofa.ark.spi.service.extension.ArkServiceLoader; import com.alipay.sofa.ark.spi.service.extension.ExtensionLoaderService; import javax.inject.Inject; import javax.inject.Singleton; /** * @author qilong.zql * @since 0.6.0 */ @Singleton public class ExtensionLoaderStage implements PipelineStage { @Inject private ExtensionLoaderService extensionLoaderService; @Override public void process(PipelineContext pipelineContext) throws ArkRuntimeException { ArkServiceLoader.setExtensionLoaderService(extensionLoaderService); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/pipeline/FinishStartupStage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.pipeline; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.event.AfterFinishStartupEvent; import com.alipay.sofa.ark.spi.pipeline.PipelineContext; import com.alipay.sofa.ark.spi.pipeline.PipelineStage; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import javax.inject.Inject; import javax.inject.Singleton; /** * @author qilong.zql * @since 0.6.0 */ @Singleton public class FinishStartupStage implements PipelineStage { @Inject private EventAdminService eventAdminService; @Override public void process(PipelineContext pipelineContext) throws ArkRuntimeException { if (ArkConfigs.isEmbedEnable()) { return; } eventAdminService.sendEvent(new AfterFinishStartupEvent()); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.pipeline; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.loader.DirectoryBizArchive; import com.alipay.sofa.ark.loader.JarBizArchive; import com.alipay.sofa.ark.spi.archive.BizArchive; import com.alipay.sofa.ark.spi.archive.ExecutableArchive; import com.alipay.sofa.ark.spi.archive.PluginArchive; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.pipeline.PipelineContext; import com.alipay.sofa.ark.spi.pipeline.PipelineStage; import com.alipay.sofa.ark.spi.service.biz.BizFactoryService; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.plugin.PluginFactoryService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import com.google.inject.Inject; import com.google.inject.Singleton; import java.net.URL; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.jar.Attributes; import static com.alipay.sofa.ark.spi.constant.Constants.ARK_BIZ_NAME; import static com.alipay.sofa.ark.spi.constant.Constants.BIZ_ACTIVE_EXCLUDE; import static com.alipay.sofa.ark.spi.constant.Constants.BIZ_ACTIVE_INCLUDE; import static com.alipay.sofa.ark.spi.constant.Constants.COMMA_SPLIT; import static com.alipay.sofa.ark.spi.constant.Constants.INJECT_EXPORT_PACKAGES; import static com.alipay.sofa.ark.spi.constant.Constants.MANIFEST_VALUE_SPLIT; import static com.alipay.sofa.ark.spi.constant.Constants.PLUGIN_ACTIVE_EXCLUDE; import static com.alipay.sofa.ark.spi.constant.Constants.PLUGIN_ACTIVE_INCLUDE; /** * response to handle executable fat jar, parse plugin model and biz model from it * * @author qilong.zql * @since 0.1.0 */ @Singleton public class HandleArchiveStage implements PipelineStage { @Inject private PluginManagerService pluginManagerService; @Inject private PluginFactoryService pluginFactoryService; @Inject private BizManagerService bizManagerService; @Inject private BizFactoryService bizFactoryService; @Override public void process(PipelineContext pipelineContext) throws ArkRuntimeException { try { if (ArkConfigs.isEmbedEnable()) { processEmbed(pipelineContext); return; } ExecutableArchive executableArchive = pipelineContext.getExecutableArchive(); List bizArchives = executableArchive.getBizArchives(); List pluginArchives = executableArchive.getPluginArchives(); if (useDynamicConfig()) { AssertUtils.isFalse( StringUtils.isEmpty(ArkConfigs.getStringValue(Constants.MASTER_BIZ)), "Master biz should be configured when using dynamic config."); } int bizCount = 0; for (BizArchive bizArchive : bizArchives) { // NOTE: biz name can not be null! Biz biz = bizFactoryService.createBiz(bizArchive); if (bizArchive instanceof DirectoryBizArchive) { if (!((DirectoryBizArchive) bizArchive).isTestMode()) { bizManagerService.registerBiz(biz); bizCount += 1; } } else if (useDynamicConfig()) { if (biz.getBizName().equals(ArkConfigs.getStringValue(Constants.MASTER_BIZ))) { bizManagerService.registerBiz(biz); bizCount += 1; } else { ArkLoggerFactory.getDefaultLogger().warn( "The biz of {} is ignored when using dynamic config.", biz.getIdentity()); } } else { if (!isBizExcluded(biz)) { bizManagerService.registerBiz(biz); bizCount += 1; } else { ArkLoggerFactory.getDefaultLogger().warn( String.format("The biz of %s is excluded.", biz.getIdentity())); } } } // master biz should be specified when deploy multi biz, otherwise the only biz would be token as master biz if (bizCount > 1) { AssertUtils.isFalse( StringUtils.isEmpty(ArkConfigs.getStringValue(Constants.MASTER_BIZ)), "Master biz should be configured when deploy multi biz."); String masterBizName = ArkConfigs.getStringValue(Constants.MASTER_BIZ); for (Biz biz : bizManagerService.getBizInOrder()) { if (masterBizName.equals(biz.getBizName())) { ArkClient.setMasterBiz(biz); } } } else { List bizList = bizManagerService.getBizInOrder(); if (!bizList.isEmpty() && StringUtils.isEmpty(ArkConfigs.getStringValue(Constants.MASTER_BIZ))) { ArkConfigs.putStringValue(Constants.MASTER_BIZ, bizList.get(0).getBizName()); ArkClient.setMasterBiz(bizList.get(0)); } } URL[] exportUrls = null; Set exportPackages = new HashSet<>(); Biz masterBiz = ArkClient.getMasterBiz(); for (BizArchive bizArchive : bizArchives) { Attributes mainAttributes = bizArchive.getManifest().getMainAttributes(); String bizName = mainAttributes.getValue(ARK_BIZ_NAME); // extension from master biz if (bizArchive instanceof JarBizArchive && masterBiz.getBizName().equalsIgnoreCase(bizName)) { String exportPackageStr = mainAttributes.getValue(INJECT_EXPORT_PACKAGES); exportPackages.addAll(StringUtils.strToSet(exportPackageStr, MANIFEST_VALUE_SPLIT)); exportUrls = ((JarBizArchive) bizArchive).getExportUrls(); } } for (PluginArchive pluginArchive : pluginArchives) { Plugin plugin = pluginFactoryService.createPlugin(pluginArchive, exportUrls, exportPackages); if (!isPluginExcluded(plugin)) { pluginManagerService.registerPlugin(plugin); } else { ArkLoggerFactory.getDefaultLogger().warn( String.format("The plugin of %s is excluded.", plugin.getPluginName())); } } } catch (Throwable ex) { throw new ArkRuntimeException(ex.getMessage(), ex); } } protected void processEmbed(PipelineContext pipelineContext) throws Exception { ClassLoader masterBizClassLoader = pipelineContext.getClass().getClassLoader(); Biz masterBiz = bizFactoryService.createEmbedMasterBiz(masterBizClassLoader); bizManagerService.registerBiz(masterBiz); ArkClient.setMasterBiz(masterBiz); ArkConfigs.putStringValue(Constants.MASTER_BIZ, masterBiz.getBizName()); ExecutableArchive executableArchive = pipelineContext.getExecutableArchive(); List pluginArchives = executableArchive.getPluginArchives(); for (PluginArchive pluginArchive : pluginArchives) { Plugin plugin = pluginFactoryService.createEmbedPlugin(pluginArchive, masterBizClassLoader); if (!isPluginExcluded(plugin)) { pluginManagerService.registerPlugin(plugin); } else { ArkLoggerFactory.getDefaultLogger().warn( String.format("The plugin of %s is excluded.", plugin.getPluginName())); } } } public void processStaticBizFromClasspath(PipelineContext pipelineContext) throws Exception { ExecutableArchive executableArchive = pipelineContext.getExecutableArchive(); List bizArchives = executableArchive.getBizArchives(); for (BizArchive bizArchive : bizArchives) { Biz biz = bizFactoryService.createBiz(bizArchive); bizManagerService.registerBiz(biz); } } public boolean isPluginExcluded(Plugin plugin) { String pluginName = plugin.getPluginName(); String includePluginConf = ArkConfigs.getStringValue(PLUGIN_ACTIVE_INCLUDE); String excludePluginConf = ArkConfigs.getStringValue(PLUGIN_ACTIVE_EXCLUDE); Set includePlugins = StringUtils.strToSet(includePluginConf, COMMA_SPLIT); Set excludePlugins = StringUtils.strToSet(excludePluginConf, COMMA_SPLIT); if (includePluginConf == null && excludePluginConf == null) { return false; } else if (includePluginConf == null) { return excludePlugins.contains(pluginName); } else { return !includePlugins.contains(pluginName); } } public boolean isBizExcluded(Biz biz) { String bizIdentity = biz.getIdentity(); String includeBizConf = ArkConfigs.getStringValue(BIZ_ACTIVE_INCLUDE); String excludeBizConf = ArkConfigs.getStringValue(BIZ_ACTIVE_EXCLUDE); Set includeBizs = StringUtils.strToSet(includeBizConf, COMMA_SPLIT); Set excludeBizs = StringUtils.strToSet(excludeBizConf, COMMA_SPLIT); if (includeBizConf == null && excludeBizConf == null) { return false; } else if (includeBizConf == null) { return excludeBizs.contains(bizIdentity); } else { return !includeBizs.contains(bizIdentity); } } public boolean useDynamicConfig() { return !StringUtils.isEmpty(ArkConfigs.getStringValue(Constants.CONFIG_SERVER_ADDRESS)); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/pipeline/RegisterServiceStage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.pipeline; import com.alipay.sofa.ark.container.registry.ContainerServiceProvider; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.container.service.biz.BizCommandProvider; import com.alipay.sofa.ark.container.service.biz.DefaultBizDeployer; import com.alipay.sofa.ark.container.service.plugin.PluginCommandProvider; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.pipeline.PipelineContext; import com.alipay.sofa.ark.spi.pipeline.PipelineStage; import com.alipay.sofa.ark.spi.service.PriorityOrdered; import com.alipay.sofa.ark.spi.service.biz.BizDeployer; import com.alipay.sofa.ark.spi.service.biz.BizFactoryService; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import com.alipay.sofa.ark.spi.service.plugin.PluginFactoryService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import com.alipay.sofa.ark.spi.service.registry.RegistryService; import com.alipay.sofa.ark.spi.service.session.CommandProvider; import com.google.inject.Inject; import com.google.inject.Singleton; import static com.alipay.sofa.ark.spi.constant.Constants.BIZ_COMMAND_UNIQUE_ID; import static com.alipay.sofa.ark.spi.constant.Constants.PLUGIN_COMMAND_UNIQUE_ID; /** * Handle service contained in {@link com.alipay.sofa.ark.spi.service.registry.RegistryService}, * mainly including registering service provided by ark container and service initialization * * @author qilong.zql * @since 0.4.0 */ @Singleton public class RegisterServiceStage implements PipelineStage { @Inject private RegistryService registryService; @Override public void process(PipelineContext pipelineContext) throws ArkRuntimeException { registryDefaultService(); } /** * Registry some default service */ private void registryDefaultService() { /** * some basic container service is not allowed to be override, they are only published * to be referenced by plugin and biz, even depended by other container service. */ registryService.publishService(BizManagerService.class, ArkServiceContainerHolder .getContainer().getService(BizManagerService.class), new ContainerServiceProvider( PriorityOrdered.HIGHEST_PRECEDENCE)); registryService.publishService(BizFactoryService.class, ArkServiceContainerHolder .getContainer().getService(BizFactoryService.class), new ContainerServiceProvider( PriorityOrdered.HIGHEST_PRECEDENCE)); registryService.publishService(PluginManagerService.class, ArkServiceContainerHolder .getContainer().getService(PluginManagerService.class), new ContainerServiceProvider( PriorityOrdered.HIGHEST_PRECEDENCE)); registryService.publishService(PluginFactoryService.class, ArkServiceContainerHolder .getContainer().getService(PluginFactoryService.class), new ContainerServiceProvider( PriorityOrdered.HIGHEST_PRECEDENCE)); registryService.publishService(EventAdminService.class, ArkServiceContainerHolder .getContainer().getService(EventAdminService.class), new ContainerServiceProvider( PriorityOrdered.HIGHEST_PRECEDENCE)); registryService.publishService(RegistryService.class, ArkServiceContainerHolder .getContainer().getService(RegistryService.class), new ContainerServiceProvider( PriorityOrdered.HIGHEST_PRECEDENCE)); /** * some container service which may depends on other basic container service. */ registryService.publishService(BizDeployer.class, new DefaultBizDeployer(), new ContainerServiceProvider()); registryService.publishService(CommandProvider.class, new PluginCommandProvider(), PLUGIN_COMMAND_UNIQUE_ID, new ContainerServiceProvider()); registryService.publishService(CommandProvider.class, new BizCommandProvider(), BIZ_COMMAND_UNIQUE_ID, new ContainerServiceProvider()); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/pipeline/StandardPipeline.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.pipeline; import com.alipay.sofa.ark.common.log.ArkLogger; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.container.service.ArkServiceContainer; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.pipeline.Pipeline; import com.alipay.sofa.ark.spi.pipeline.PipelineContext; import com.alipay.sofa.ark.spi.pipeline.PipelineStage; import com.google.inject.Singleton; import java.util.ArrayList; import java.util.List; /** * Standard Pipeline Implementation * * @author ruoshan * @since 0.1.0 */ @Singleton public class StandardPipeline implements Pipeline { private List stages = new ArrayList<>(); public StandardPipeline() { initializePipeline(); } private void initializePipeline() { addPipelineStage( ArkServiceContainerHolder.getContainer().getService(HandleArchiveStage.class)) .addPipelineStage( ArkServiceContainerHolder.getContainer().getService(RegisterServiceStage.class)) .addPipelineStage( ArkServiceContainerHolder.getContainer().getService(ExtensionLoaderStage.class)) .addPipelineStage( ArkServiceContainerHolder.getContainer().getService(DeployPluginStage.class)) .addPipelineStage( ArkServiceContainerHolder.getContainer().getService(DeployBizStage.class)) .addPipelineStage( ArkServiceContainerHolder.getContainer().getService(FinishStartupStage.class)); } @Override public Pipeline addPipelineStage(PipelineStage pipelineStage) { stages.add(pipelineStage); return this; } @Override public void process(PipelineContext pipelineContext) throws ArkRuntimeException { for (PipelineStage pipelineStage : stages) { try { ArkLoggerFactory.getDefaultLogger().info( String.format("Start to process pipeline stage: %s", pipelineStage.getClass() .getName())); pipelineStage.process(pipelineContext); ArkLoggerFactory.getDefaultLogger().info( String.format("Finish to process pipeline stage: %s", pipelineStage.getClass() .getName())); } catch (Throwable e) { ArkLoggerFactory.getDefaultLogger().error( String.format("Process pipeline stage fail: %s", pipelineStage.getClass() .getName()), e); throw new ArkRuntimeException(e); } } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/registry/AbstractServiceProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.registry; import com.alipay.sofa.ark.spi.registry.ServiceProvider; import com.alipay.sofa.ark.spi.registry.ServiceProviderType; import com.alipay.sofa.ark.spi.service.PriorityOrdered; /** * Abstract Service Provider * * @author qilong.zql * @since 0.4.0 */ public abstract class AbstractServiceProvider implements ServiceProvider { protected ServiceProviderType providerType; public AbstractServiceProvider(ServiceProviderType providerType) { this.providerType = providerType; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || obj.getClass() != this.getClass()) { return false; } if (getPriority() != ((PriorityOrdered) obj).getPriority()) { return false; } return getServiceProviderType() == ((ServiceProvider) obj).getServiceProviderType(); } @Override public ServiceProviderType getServiceProviderType() { return providerType; } @Override public String getServiceProviderDesc() { return getServiceProviderType().getDesc(); } @Override public String toString() { return String.format("ServiceProvider{provider=\'%s\', order=%d}", getServiceProviderDesc(), getPriority()); } @Override public int hashCode() { int result = getServiceProviderType().hashCode(); result = 31 * result + getPriority(); return result; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/registry/ContainerServiceProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.registry; import com.alipay.sofa.ark.spi.registry.ServiceProviderType; /** * Ark Container Service Provider, default service provider if provider is not set * * @author ruoshan * @since 0.1.0 */ public class ContainerServiceProvider extends AbstractServiceProvider { private int order; public ContainerServiceProvider() { this(DEFAULT_PRECEDENCE); } public ContainerServiceProvider(int order) { super(ServiceProviderType.ARK_CONTAINER); this.order = order; } @Override public int getPriority() { return order; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/registry/DefaultServiceFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.registry; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.spi.registry.*; /** * Filter Service according to the given {@linkplain com.alipay.sofa.ark.spi.registry.ServiceProvider} * * @author qilong.zql * @since 0.4.0 */ public class DefaultServiceFilter implements ServiceFilter { private ServiceProviderType providerType; private Class serviceInterface; private String uniqueId; @Override public boolean match(ServiceReference serviceReference) { AssertUtils.assertNotNull(serviceReference, "ServiceReference should not be null"); ServiceMetadata serviceMetadata = serviceReference.getServiceMetadata(); ServiceProvider provider = serviceMetadata.getServiceProvider(); boolean isMatch = matchProviderType(provider.getServiceProviderType()); isMatch &= matchServiceInterface(serviceMetadata.getInterfaceClass()); isMatch &= matchUniqueId(serviceMetadata.getUniqueId()); return isMatch; } private boolean matchProviderType(ServiceProviderType serviceProviderType) { if (providerType == null) { return true; } return providerType.equals(serviceProviderType); } private boolean matchServiceInterface(Class serviceInterface) { if (this.serviceInterface == null) { return true; } return this.serviceInterface.equals(serviceInterface); } private boolean matchUniqueId(String uniqueId) { if (this.uniqueId == null) { return true; } return this.uniqueId.equals(uniqueId); } public ServiceProviderType getProviderType() { return providerType; } public DefaultServiceFilter setProviderType(ServiceProviderType providerType) { this.providerType = providerType; return this; } public Class getServiceInterface() { return serviceInterface; } public DefaultServiceFilter setServiceInterface(Class serviceInterface) { this.serviceInterface = serviceInterface; return this; } public String getUniqueId() { return uniqueId; } public DefaultServiceFilter setUniqueId(String uniqueId) { this.uniqueId = uniqueId; return this; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/registry/PluginServiceProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.registry; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.registry.ServiceProviderType; /** * Plugin Service Provider, when service is published by plugin, then use this provider * * @author ruoshan * @since 0.1.0 */ public class PluginServiceProvider extends AbstractServiceProvider { private Plugin plugin; public PluginServiceProvider(Plugin plugin) { super(ServiceProviderType.ARK_PLUGIN); AssertUtils.assertNotNull(plugin, "Plugin should not be null."); this.plugin = plugin; } @Override public String getServiceProviderDesc() { return String.format("%s:%s", super.getServiceProviderDesc(), plugin.getPluginName()); } @Override public int getPriority() { return plugin.getPriority(); } public String getPluginName() { return plugin.getPluginName(); } public Plugin getPlugin() { return plugin; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + plugin.hashCode(); return result; } @Override public boolean equals(Object obj) { if (!super.equals(obj)) { return false; } PluginServiceProvider serviceProvider = (PluginServiceProvider) obj; return plugin.equals(serviceProvider.getPlugin()); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/registry/ServiceMetadataImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.registry; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.spi.registry.ServiceMetadata; import com.alipay.sofa.ark.spi.registry.ServiceProvider; /** * Service Metadata Implement * * @author ruoshan * @since 0.1.0 */ public class ServiceMetadataImpl implements ServiceMetadata { private String uniqueId; private Class interfaceClass; private ServiceProvider serviceProvider; public ServiceMetadataImpl(Class interfaceClass, String uniqueId, ServiceProvider serviceProvider) { AssertUtils.assertNotNull(interfaceClass, "Service interface should not be null."); AssertUtils.assertNotNull(uniqueId, "Service uniqueId should not be null"); AssertUtils.assertNotNull(serviceProvider, "Service provider should not be null."); this.uniqueId = uniqueId; this.interfaceClass = interfaceClass; this.serviceProvider = serviceProvider; } @Override public String getUniqueId() { return uniqueId; } @Override public Class getInterfaceClass() { return interfaceClass; } @Override public ServiceProvider getServiceProvider() { return serviceProvider; } @Override public String getServiceName() { if (StringUtils.isEmpty(uniqueId)) { return interfaceClass.getCanonicalName(); } else { return String.format("%s:%s", interfaceClass.getCanonicalName(), uniqueId); } } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } ServiceMetadata serviceMetadata = (ServiceMetadata) obj; if (!uniqueId.equals(serviceMetadata.getUniqueId())) { return false; } if (!interfaceClass.equals(serviceMetadata.getInterfaceClass())) { return false; } return serviceProvider.equals(serviceMetadata.getServiceProvider()); } @Override public int hashCode() { int result = 1; result = 31 * result + uniqueId.hashCode(); result = 31 * result + interfaceClass.hashCode(); result = 31 * result + serviceProvider.hashCode(); return result; } @Override public String toString() { return String.format("ServiceMetadata{service=\'%s\', provider=\'%s\'}", getServiceName(), getServiceProvider().toString()); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/registry/ServiceReferenceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.registry; import com.alipay.sofa.ark.spi.registry.ServiceMetadata; import com.alipay.sofa.ark.spi.registry.ServiceReference; /** * Service Reference Implement * * @author ruoshan * @since 0.1.0 */ public class ServiceReferenceImpl implements ServiceReference { private ServiceMetadata serviceMetadata; private T serviceObject; public ServiceReferenceImpl(ServiceMetadata serviceMetadata, T serviceObject) { this.serviceMetadata = serviceMetadata; this.serviceObject = serviceObject; } @Override public T getService() { return serviceObject; } @Override public ServiceMetadata getServiceMetadata() { return serviceMetadata; } @Override public int getPriority() { return getServiceMetadata().getServiceProvider().getPriority(); } @Override public int hashCode() { return serviceMetadata.hashCode(); } @Override public String toString() { return serviceMetadata.toString(); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/ArkServiceContainer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.common.guice.AbstractArkGuiceModule; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.common.util.OrderComparator; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.service.ArkService; import com.alipay.sofa.ark.spi.service.biz.BizFactoryService; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import com.alipay.sofa.ark.spi.service.injection.InjectionService; import com.alipay.sofa.ark.spi.service.plugin.PluginFactoryService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import com.google.inject.Binding; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.TypeLiteral; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.ServiceLoader; import java.util.concurrent.atomic.AtomicBoolean; /** * Ark Service Container * * @author ruoshan * @since 0.1.0 */ public class ArkServiceContainer { private Injector injector; private List arkServiceList = new ArrayList<>(); private AtomicBoolean started = new AtomicBoolean(false); private AtomicBoolean stopped = new AtomicBoolean(false); private final String[] arguments; public ArkServiceContainer(String[] arguments) { this.arguments = arguments; } /** * Start Ark Service Container * @throws ArkRuntimeException * @since 0.1.0 */ public void start() throws ArkRuntimeException { if (started.compareAndSet(false, true)) { ClassLoader oldClassLoader = ClassLoaderUtils.pushContextClassLoader(getClass() .getClassLoader()); try { ArkLoggerFactory.getDefaultLogger().info("Begin to start ArkServiceContainer"); injector = Guice.createInjector(findServiceModules()); for (Binding binding : injector .findBindingsByType(new TypeLiteral() { })) { arkServiceList.add(binding.getProvider().get()); } Collections.sort(arkServiceList, new OrderComparator()); for (ArkService arkService : arkServiceList) { ArkLoggerFactory.getDefaultLogger().info( String.format("Init Service: %s", arkService.getClass().getName())); arkService.init(); } ArkServiceContainerHolder.setContainer(this); ArkClient.setBizFactoryService(getService(BizFactoryService.class)); ArkClient.setBizManagerService(getService(BizManagerService.class)); ArkClient.setInjectionService(getService(InjectionService.class)); ArkClient.setEventAdminService(getService(EventAdminService.class)); ArkClient.setPluginManagerService(getService(PluginManagerService.class)); ArkClient.setPluginFactoryService(getService(PluginFactoryService.class)); ArkClient.setArguments(arguments); ArkLoggerFactory.getDefaultLogger().info("Finish to start ArkServiceContainer"); } finally { ClassLoaderUtils.popContextClassLoader(oldClassLoader); } } } private List findServiceModules() throws ArkRuntimeException { try { List modules = new ArrayList<>(); for (AbstractArkGuiceModule module : ServiceLoader.load(AbstractArkGuiceModule.class)) { modules.add(module); } return modules; } catch (Throwable e) { throw new ArkRuntimeException(e); } } /** * Get Service from ArkService Container * @param clazz * @param * @return * @since 0.1.0 */ public T getService(Class clazz) { return injector.getInstance(clazz); } /** * Stop Ark Service Container * @throws ArkRuntimeException * @since 0.1.0 */ public void stop() throws ArkRuntimeException { if (stopped.compareAndSet(false, true)) { ArkLoggerFactory.getDefaultLogger().info("Begin to stop ArkServiceContainer"); ClassLoader oldClassLoader = ClassLoaderUtils.pushContextClassLoader(getClass() .getClassLoader()); try { Collections.reverse(arkServiceList); for (ArkService arkService : arkServiceList) { ArkLoggerFactory.getDefaultLogger().info( String.format("Dispose service: %s", arkService.getClass().getName())); arkService.dispose(); } ArkLoggerFactory.getDefaultLogger().info("Finish to stop ArkServiceContainer"); } finally { ClassLoaderUtils.popContextClassLoader(oldClassLoader); } } } /** * Whether Ark Service Container is started or not * @return * @since 0.1.0 */ public boolean isStarted() { return started.get(); } /** * Whether Ark Service Container is running or not * @return * @since 0.1.0 */ public boolean isRunning() { return isStarted() && !stopped.get(); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/ArkServiceContainerHolder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service; /** * Ark Service Container Holder * * @author ruoshan * @since 0.1.0 */ public class ArkServiceContainerHolder { private static ArkServiceContainer container; public static ArkServiceContainer getContainer() { return container; } public static void setContainer(ArkServiceContainer container) { ArkServiceContainerHolder.container = container; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizCommandProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.biz; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.thread.ThreadPoolManager; import com.alipay.sofa.ark.common.util.EnvironmentUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizOperation; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.service.ArkInject; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.session.CommandProvider; import java.net.URL; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; /** * @author qilong.zql * @since 0.6.0 */ public class BizCommandProvider implements CommandProvider { @ArkInject private BizManagerService bizManagerService; @Override public String getHelp() { return HELP_MESSAGE; } @Override public String handleCommand(String command) { return new BizCommand(command).process(); } @Override public boolean validate(String command) { return new BizCommand(command).isValidate(); } static final String HELP_MESSAGE = "Biz Command Tips:\n" + " USAGE: biz [option...] [arguments...]\n" + " SAMPLE: biz -m bizIdentityA bizIdentityB.\n" + " -h Shows the help message.\n" + " -a Shows all biz.\n" + " -m Shows the meta info of specified bizIdentity.\n" + " -s Shows the service info of specified bizIdentity.\n" + " -d Shows the detail info of specified bizIdentity.\n" + " -i Install biz of specified bizIdentity or bizUrl.\n" + " -u Uninstall biz of specified bizIdentity.\n" + " -o Switch biz of specified bizIdentity.\n"; class BizCommand { private boolean isValidate; private Set options = new HashSet<>(); private Set parameters = new HashSet<>(); BizCommand(String command) { if (StringUtils.isEmpty(command)) { isValidate = false; return; } String[] syntax = command.trim().split(Constants.SPACE_SPLIT); if (!"biz".equals(syntax[0])) { isValidate = false; return; } int argumentIndex = syntax.length; // fetch all options and allow repetition for (int i = 1; i < syntax.length; ++i) { if (!syntax[i].startsWith("-")) { argumentIndex = i; break; } if (syntax[i].startsWith("-") && syntax[i].length() == 1) { isValidate = false; return; } for (int j = 1; j < syntax[i].length(); ++j) { options.add(syntax[i].charAt(j)); } } // only the following option can be allowed. for (Character option : options) { switch (option) { case 'h': case 'a': case 'm': case 's': case 'd': case 'i': case 'u': case 'o': continue; default: isValidate = false; return; } } // check whether options is empty if (options.isEmpty()) { isValidate = false; return; } // '-h' or '-a' option can not be combined with other option, such as '-m' if (options.contains('h') || options.contains('a') || options.contains('i') || options.contains('u') || options.contains('o')) { if (options.size() > 1) { isValidate = false; return; } } // take the rest option as parameters while (argumentIndex < syntax.length) { parameters.add(syntax[argumentIndex++]); } // '-h' or '-a' option need not any parameter if ((options.contains('h') || options.contains('a')) && parameters.size() > 0) { isValidate = false; return; } // if option is not 'h' or 'a', parameter should not be empty if (!(options.contains('h') || options.contains('a')) && parameters.isEmpty()) { isValidate = false; return; } // if option is 'i' or 'u' or 'o', parameter count should be only one. if (options.contains('i') || options.contains('u') || options.contains('o')) { if (parameters.size() > 1) { isValidate = false; return; } } isValidate = true; } boolean isValidate() { return isValidate; } String process() { if (!isValidate) { return "Error command format. Pls type 'biz -h' to get help message\n"; } StringBuilder sb = new StringBuilder(512); if (options.contains('h')) { return HELP_MESSAGE; } else if (options.contains('a')) { return bizList(); } else if (options.contains('i')) { return installBiz(); } else if (options.contains('u')) { return uninstallBiz(); } else if (options.contains('o')) { return switchBiz(); } else { Set candidates = bizManagerService.getAllBizIdentities(); boolean matched = false; for (String pattern : parameters) { for (String candidate : candidates) { if (Pattern.matches(pattern, candidate)) { matched = true; sb.append(bizInfo(candidate)); } } } if (!matched) { sb.append("no matched biz candidates.").append("\n"); } } return sb.toString(); } String bizList() { List bizList = bizManagerService.getBizInOrder(); StringBuilder sb = new StringBuilder(128); for (Biz biz : bizList) { sb.append(biz.getIdentity()).append(Constants.STRING_COLON) .append(biz.getBizState()).append("\n"); } sb.append("biz count = ").append(bizList.size()).append("\n"); return sb.toString(); } String installBiz() { if (EnvironmentUtils.isOpenSecurity()) { return "Cannot execute install command in security mode.\n"; } if (!isReadyInstall()) { return "Exists some biz whose state is neither 'activated' nor 'deactivated'.\n"; } ThreadPoolManager.getThreadPool(Constants.TELNET_COMMAND_THREAD_POOL_NAME) .getExecutor().execute(new Runnable() { @Override public void run() { BizOperation bizOperation = new BizOperation() .setOperationType(BizOperation.OperationType.INSTALL); String param = parameters.toArray(new String[] {})[0]; try { URL url = new URL(param); bizOperation.putParameter(Constants.CONFIG_BIZ_URL, param); } catch (Throwable t) { String[] nameAndVersion = param.split(Constants.STRING_COLON); if (nameAndVersion.length != 2) { ArkLoggerFactory.getDefaultLogger().error( "Invalid telnet biz install command {}", param); return; } bizOperation.setBizName(nameAndVersion[0]).setBizVersion( nameAndVersion[1]); } try { ArkClient.installOperation(bizOperation); } catch (Throwable throwable) { ArkLoggerFactory.getDefaultLogger().error( "Fail to process telnet install command: " + param, throwable); } } }); return "Start to process install command now, pls wait and check."; } String uninstallBiz() { if (EnvironmentUtils.isOpenSecurity()) { return "Cannot execute uninstall command in security mode.\n"; } ThreadPoolManager.getThreadPool(Constants.TELNET_COMMAND_THREAD_POOL_NAME) .getExecutor().execute(new Runnable() { @Override public void run() { String param = parameters.toArray(new String[] {})[0]; String[] nameAndVersion = param.split(Constants.STRING_COLON); if (nameAndVersion.length != 2) { ArkLoggerFactory.getDefaultLogger().error( "Invalid telnet biz uninstall command {}", param); return; } try { ArkClient.uninstallBiz(nameAndVersion[0], nameAndVersion[1]); } catch (Throwable throwable) { ArkLoggerFactory.getDefaultLogger().error( "Fail to process telnet uninstall command: " + param, throwable); } } }); return "Start to process uninstall command now, pls wait and check."; } String switchBiz() { ThreadPoolManager.getThreadPool(Constants.TELNET_COMMAND_THREAD_POOL_NAME) .getExecutor().execute(new Runnable() { @Override public void run() { String param = parameters.toArray(new String[] {})[0]; String[] nameAndVersion = param.split(Constants.STRING_COLON); if (nameAndVersion.length != 2) { ArkLoggerFactory.getDefaultLogger().error( "Invalid telnet biz switch command {}", param); return; } try { ArkClient.switchBiz(nameAndVersion[0], nameAndVersion[1]); } catch (Throwable throwable) { ArkLoggerFactory.getDefaultLogger().error( "Fail to process telnet switch command: " + param, throwable); } } }); return "Start to process switch command now, pls wait and check."; } String bizInfo(String bizIdentity) { Biz biz = bizManagerService.getBizByIdentity(bizIdentity); if (biz == null) { return "Invalid bizIdentity: " + bizIdentity + "\n"; } StringBuilder sb = new StringBuilder(256); // print biz meta info if (options.contains('m')) { sb.append("BizName: ").append(biz.getBizName()).append("\n"); sb.append("Version: ").append(biz.getBizVersion()).append("\n"); sb.append("Priority: ").append(biz.getPriority()).append("\n"); sb.append("MainClass: ").append(biz.getMainClass()).append("\n"); sb.append("WebContextPath: ").append(biz.getWebContextPath()) .append("\n"); sb.append("Deny Import Packages: ") .append(StringUtils.setToStr(biz.getDenyImportPackages(), ",", "\\")) .append("\n"); sb.append("Deny Import Classes: ") .append(StringUtils.setToStr(biz.getDenyImportClasses(), ",", "\\")) .append("\n"); sb.append("Deny Import Resources: ") .append(StringUtils.setToStr(biz.getDenyImportResources(), ",", "\\")) .append("\n"); } // print biz service info if (options.contains('s')) { // TODO } // print biz detail info if (options.contains('d')) { sb.append("ClassLoader: ").append(biz.getBizClassLoader()).append("\n"); sb.append("ClassPath: ").append(join(biz.getClassPath(), ",")).append("\n"); } sb.append("\n"); return sb.toString(); } public boolean isReadyInstall() { for (Biz biz : bizManagerService.getBizInOrder()) { if (biz.getBizState() != BizState.ACTIVATED && biz.getBizState() != BizState.DEACTIVATED) { return false; } } return true; } String join(URL[] urls, String separator) { Set set = new HashSet<>(); if (urls != null) { for (URL url : urls) { set.add(url.getPath()); } } return StringUtils.setToStr(set, separator, "\\"); } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizDeployServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.biz; import com.alipay.sofa.ark.common.log.ArkLogger; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.registry.ServiceReference; import com.alipay.sofa.ark.spi.service.biz.BizDeployService; import com.alipay.sofa.ark.spi.service.biz.BizDeployer; import com.alipay.sofa.ark.spi.service.registry.RegistryService; import javax.inject.Inject; import javax.inject.Singleton; /** * Service implementation to deploy Biz * * @author qilong.zql * @since 0.4.0 */ @Singleton public class BizDeployServiceImpl implements BizDeployService { @Inject private RegistryService registryService; private BizDeployer bizDeployer; @Override public void deploy(String[] args) throws ArkRuntimeException { ServiceReference serviceReference = registryService .referenceService(BizDeployer.class); bizDeployer = serviceReference.getService(); ArkLoggerFactory.getDefaultLogger().info( String.format("BizDeployer=\'%s\' is starting.", bizDeployer.getDesc())); bizDeployer.init(args); bizDeployer.deploy(); } @Override public void unDeploy() throws ArkRuntimeException { if (bizDeployer != null) { ArkLoggerFactory.getDefaultLogger().info( String.format("BizDeployer=\'%s\' is stopping.", bizDeployer.getDesc())); bizDeployer.unDeploy(); } } @Override public void init() throws ArkRuntimeException { // no action } @Override public void dispose() throws ArkRuntimeException { unDeploy(); } @Override public int getPriority() { return DEFAULT_PRECEDENCE; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizFactoryServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.biz; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.service.classloader.BizClassLoader; import com.alipay.sofa.ark.loader.ExplodedBizArchive; import com.alipay.sofa.ark.loader.DirectoryBizArchive; import com.alipay.sofa.ark.loader.JarBizArchive; import com.alipay.sofa.ark.loader.archive.JarFileArchive; import com.alipay.sofa.ark.loader.jar.JarFile; import com.alipay.sofa.ark.spi.archive.Archive; import com.alipay.sofa.ark.spi.archive.BizArchive; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.*; import com.alipay.sofa.ark.spi.model.BizInfo.StateChangeReason; import com.alipay.sofa.ark.spi.service.biz.BizFactoryService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import com.google.inject.Inject; import com.google.inject.Singleton; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.jar.Attributes; import java.util.stream.Stream; import static com.alipay.sofa.ark.spi.constant.Constants.*; /** * {@link BizFactoryService} * * @author qilong.zql * @since 0.4.0 */ @Singleton public class BizFactoryServiceImpl implements BizFactoryService { @Inject private PluginManagerService pluginManagerService; @Override public Biz createBiz(BizArchive bizArchive) throws IOException { return createBiz(bizArchive, new BizConfig()); } @Override public Biz createBiz(BizArchive bizArchive, URL[] extensionUrls) throws IOException { BizConfig bizConfig = new BizConfig(); bizConfig.setExtensionUrls(extensionUrls); return createBiz(bizArchive, bizConfig); } @Override public Biz createBiz(File file) throws IOException { BizArchive bizArchive = prepareBizArchive(file); return createBiz(bizArchive, new BizConfig()); } @Override public Biz createBiz(File file, URL[] extensionUrls) throws IOException { BizArchive bizArchive = prepareBizArchive(file); BizConfig bizConfig = new BizConfig(); bizConfig.setExtensionUrls(extensionUrls); return createBiz(bizArchive, bizConfig); } @Override public Biz createBiz(BizOperation bizOperation, File file) throws IOException { BizArchive bizArchive = prepareBizArchive(file); BizConfig bizConfig = new BizConfig(); bizConfig.setSpecifiedVersion(bizOperation.getBizVersion()); return createBiz(bizArchive, bizConfig); } @Override public Biz createBiz(File file, BizConfig bizConfig) throws IOException { BizArchive bizArchive = prepareBizArchive(file); return createBiz(bizArchive, bizConfig); } @Override public Biz createBiz(BizArchive bizArchive, BizConfig bizConfig) throws IOException { AssertUtils.isTrue(isArkBiz(bizArchive), "Archive must be a ark biz!"); AssertUtils.isTrue(bizConfig != null, "BizConfig must not be null!"); Attributes manifestMainAttributes = bizArchive.getManifest().getMainAttributes(); String mainClass = manifestMainAttributes.getValue(MAIN_CLASS_ATTRIBUTE); String startClass = manifestMainAttributes.getValue(START_CLASS_ATTRIBUTE); BizModel bizModel = new BizModel(); bizModel .setBizState(BizState.RESOLVED, StateChangeReason.CREATED) .setBizName(manifestMainAttributes.getValue(ARK_BIZ_NAME)) .setBizVersion( !StringUtils.isEmpty(bizConfig.getSpecifiedVersion()) ? bizConfig .getSpecifiedVersion() : manifestMainAttributes.getValue(ARK_BIZ_VERSION)) .setBizUrl(!(bizArchive instanceof DirectoryBizArchive) ? bizArchive.getUrl() : null) .setMainClass(!StringUtils.isEmpty(startClass) ? startClass : mainClass) .setPriority(manifestMainAttributes.getValue(PRIORITY_ATTRIBUTE)) .setWebContextPath(manifestMainAttributes.getValue(WEB_CONTEXT_PATH)) .setDenyImportPackages(manifestMainAttributes.getValue(DENY_IMPORT_PACKAGES)) .setDenyImportClasses(manifestMainAttributes.getValue(DENY_IMPORT_CLASSES)) .setDenyImportResources(manifestMainAttributes.getValue(DENY_IMPORT_RESOURCES)) .setInjectPluginDependencies( getInjectDependencies(manifestMainAttributes.getValue(INJECT_PLUGIN_DEPENDENCIES))) .setInjectExportPackages(manifestMainAttributes.getValue(INJECT_EXPORT_PACKAGES)) .setDeclaredLibraries(manifestMainAttributes.getValue(DECLARED_LIBRARIES)) .setClassPath(getMergedBizClassPath(bizArchive.getUrls(), bizConfig.getExtensionUrls())); // prepare dependent plugins and plugin export map List dependentPlugins = bizConfig.getDependentPlugins(); if (dependentPlugins == null || dependentPlugins.isEmpty()) { dependentPlugins = StringUtils.strToList( manifestMainAttributes.getValue("dependent-plugins"), Constants.MANIFEST_VALUE_SPLIT); } resolveExportMapIfNecessary(bizModel, dependentPlugins); // must be after prepare dependent plugins bizModel.setPluginClassPath(getPluginURLs(bizModel)); // create biz classloader BizClassLoader bizClassLoader = new BizClassLoader(bizModel.getIdentity(), getBizUcp(bizModel), bizArchive instanceof ExplodedBizArchive || bizArchive instanceof DirectoryBizArchive); bizClassLoader.setBizModel(bizModel); bizModel.setClassLoader(bizClassLoader); // set biz work dir if (bizModel.getBizUrl() != null) { bizModel.setBizTempWorkDir(new File(bizModel.getBizUrl().getFile())); } return bizModel; } @Override public Biz createEmbedMasterBiz(ClassLoader masterClassLoader) { BizModel bizModel = new BizModel(); bizModel.setBizState(BizState.RESOLVED, StateChangeReason.CREATED) .setBizName(ArkConfigs.getStringValue(MASTER_BIZ)).setBizVersion("1.0.0") .setMainClass("embed main").setPriority("100").setWebContextPath("/") .setDenyImportPackages(null).setDenyImportClasses(null).setDenyImportResources(null) .setInjectPluginDependencies(new HashSet<>()).setInjectExportPackages(null) .setClassPath(ClassLoaderUtils.getURLs(masterClassLoader)) .setClassLoader(masterClassLoader); return bizModel; } private BizArchive prepareBizArchive(File file) throws IOException { BizArchive bizArchive; boolean unpackBizWhenInstall = Boolean.parseBoolean(ArkConfigs.getStringValue( UNPACK_BIZ_WHEN_INSTALL, "true")); if (ArkConfigs.isEmbedEnable() && unpackBizWhenInstall) { File unpackFile = FileUtils.file(file.getAbsolutePath() + "-unpack"); if (!unpackFile.exists()) { unpackFile = FileUtils.unzip(file, file.getAbsolutePath() + "-unpack"); } if (file.exists()) { file.delete(); } file = unpackFile; bizArchive = new ExplodedBizArchive(unpackFile); } else { JarFile bizFile = new JarFile(file); JarFileArchive jarFileArchive = new JarFileArchive(bizFile); bizArchive = new JarBizArchive(jarFileArchive); } return bizArchive; } private URL[] getMergedBizClassPath(URL[] bizArchiveUrls, URL[] extensionUrls) { if (extensionUrls == null || extensionUrls.length == 0) { return bizArchiveUrls; } return Stream.concat(Arrays.stream(bizArchiveUrls), Arrays.stream(extensionUrls)).toArray(URL[]::new); } private void resolveExportMapIfNecessary(BizModel bizModel, List dependentPlugins) { Set plugins = new HashSet<>(); if (ArkConfigs.isBizSpecifyDependentPluginsEnable()) { if (dependentPlugins != null && !dependentPlugins.isEmpty()) { for (String pluginName : dependentPlugins) { Plugin plugin = pluginManagerService.getPluginByName(pluginName); plugins.add(plugin); } } } else { plugins.addAll(pluginManagerService.getPluginsInOrder()); } bizModel.setDependentPlugins(plugins); for (Plugin plugin : plugins) { for (String exportIndex : plugin.getExportPackageNodes()) { bizModel.getExportNodeAndClassLoaderMap().putIfAbsent(exportIndex, plugin); } for (String exportIndex : plugin.getExportPackageStems()) { bizModel.getExportStemAndClassLoaderMap().putIfAbsent(exportIndex, plugin); } for (String exportIndex : plugin.getExportClasses()) { bizModel.getExportClassAndClassLoaderMap().putIfAbsent(exportIndex, plugin); } for (String resource : plugin.getExportResources()) { bizModel.getExportResourceAndClassLoaderMap().putIfAbsent(resource, new LinkedList<>()); bizModel.getExportResourceAndClassLoaderMap().get(resource).add(plugin); } for (String resource : plugin.getExportPrefixResourceStems()) { bizModel.getExportPrefixStemResourceAndClassLoaderMap().putIfAbsent(resource, new LinkedList<>()); bizModel.getExportPrefixStemResourceAndClassLoaderMap().get(resource).add(plugin); } for (String resource : plugin.getExportSuffixResourceStems()) { bizModel.getExportSuffixStemResourceAndClassLoaderMap().putIfAbsent(resource, new LinkedList<>()); bizModel.getExportSuffixStemResourceAndClassLoaderMap().get(resource).add(plugin); } } } private Set getInjectDependencies(String injectPluginDependencies) { Set dependencies = new HashSet<>(); if (StringUtils.strToSet(injectPluginDependencies, Constants.MANIFEST_VALUE_SPLIT) != null) { dependencies.addAll(StringUtils.strToSet(injectPluginDependencies, Constants.MANIFEST_VALUE_SPLIT)); } return dependencies; } private boolean isArkBiz(BizArchive bizArchive) { if (ArkConfigs.isEmbedEnable() && bizArchive instanceof ExplodedBizArchive) { return true; } return bizArchive.isEntryExist(new Archive.EntryFilter() { @Override public boolean matches(Archive.Entry entry) { return !entry.isDirectory() && entry.getName().equals(Constants.ARK_BIZ_MARK_ENTRY); } }); } private URL[] getBizUcp(BizModel bizModel) { List bizUcp = new ArrayList<>(); bizUcp.addAll(Arrays.asList(bizModel.getClassPath())); bizUcp.addAll(Arrays.asList(getPluginURLs(bizModel))); return bizUcp.toArray(new URL[bizUcp.size()]); } private URL[] getPluginURLs(BizModel bizModel) { List pluginUrls = new ArrayList<>(); for (Plugin plugin : bizModel.getDependentPlugins()) { pluginUrls.add(plugin.getPluginURL()); } return pluginUrls.toArray(new URL[pluginUrls.size()]); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.biz; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.BizIdentityUtils; import com.alipay.sofa.ark.common.util.OrderComparator; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizInfo.StateChangeReason; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.google.inject.Singleton; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; /** * Service Implementation to manager ark biz * * @author ruoshan * @since 0.1.0 */ @Singleton public class BizManagerServiceImpl implements BizManagerService { private final ConcurrentHashMap> bizRegistration = new ConcurrentHashMap<>(); private final ConcurrentHashMap bizLocks = new ConcurrentHashMap<>(); @Override public ReentrantLock getBizLock(String bizName) { AssertUtils.isFalse(StringUtils.isEmpty(bizName), "Biz name must not be empty."); bizLocks.putIfAbsent(bizName, new ReentrantLock()); return bizLocks.get(bizName); } @Override public boolean registerBiz(Biz biz) { AssertUtils.assertNotNull(biz, "Biz must not be null."); AssertUtils.isTrue(biz.getBizState() == BizState.RESOLVED, "BizState must be RESOLVED."); // Two level cache here. First level cache key is biz name, and value is versions cache. // Second level cache key is version, value is biz model. bizRegistration.putIfAbsent(biz.getBizName(), new ConcurrentHashMap<>(16)); ConcurrentHashMap bizCache = bizRegistration.get(biz.getBizName()); return bizCache.put(biz.getBizVersion(), biz) == null; } @Override public Biz unRegisterBiz(String bizName, String bizVersion) { AssertUtils.isTrue(getBizState(bizName, bizVersion) != BizState.RESOLVED, "Biz whose state is resolved must not be un-registered."); return unRegisterBizStrictly(bizName, bizVersion); } @Override public Biz unRegisterBizStrictly(String bizName, String bizVersion) { AssertUtils.isFalse(StringUtils.isEmpty(bizName), "Biz name must not be empty."); AssertUtils.isFalse(StringUtils.isEmpty(bizVersion), "Biz version must not be empty."); ConcurrentHashMap bizCache = bizRegistration.get(bizName); if (bizCache != null) { return bizCache.remove(bizVersion); } return null; } @Override public List getBiz(String bizName) { AssertUtils.isFalse(StringUtils.isEmpty(bizName), "Biz name must not be empty."); ConcurrentHashMap bizCache = bizRegistration.get(bizName); List bizList = new ArrayList<>(); if (bizCache != null) { bizList.addAll(bizCache.values()); } return bizList; } @Override public Biz getBiz(String bizName, String bizVersion) { AssertUtils.isFalse(StringUtils.isEmpty(bizName), "Biz name must not be empty."); AssertUtils.isFalse(StringUtils.isEmpty(bizVersion), "Biz version must not be empty."); ConcurrentHashMap bizCache = bizRegistration.get(bizName); if (bizCache != null) { return bizCache.get(bizVersion); } return null; } @Override public Biz getBizByIdentity(String bizIdentity) { AssertUtils.isTrue(BizIdentityUtils.isValid(bizIdentity), "Format of Biz Identity is error."); String[] str = bizIdentity.split(Constants.STRING_COLON); return getBiz(str[0], str[1]); } @Override public Biz getBizByClassLoader(ClassLoader classLoader) { for (Map.Entry> bizMapEntry : bizRegistration .entrySet()) { for (Map.Entry bizEntry : bizMapEntry.getValue().entrySet()) { Biz biz = bizEntry.getValue(); if (biz.getBizClassLoader().equals(classLoader)) { return biz; } } } return null; } @Override public Set getAllBizNames() { return bizRegistration.keySet(); } @Override public Set getAllBizIdentities() { Set bizIdentities = new HashSet<>(); for (Biz biz : getBizInOrder()) { bizIdentities.add(biz.getIdentity()); } return bizIdentities; } @Override public List getBizInOrder() { List bizList = new ArrayList<>(); for (String bizName : bizRegistration.keySet()) { bizList.addAll(bizRegistration.get(bizName).values()); } Collections.sort(bizList, new OrderComparator()); return bizList; } @Override public Biz getActiveBiz(String bizName) { AssertUtils.isFalse(StringUtils.isEmpty(bizName), "Biz name must not be empty."); Map bizCache = bizRegistration.get(bizName); if (bizCache != null) { for (Biz biz : bizCache.values()) { if (biz.getBizState() == BizState.ACTIVATED) { return biz; } } } return null; } @Override public boolean isActiveBiz(String bizName, String bizVersion) { AssertUtils.isFalse(StringUtils.isEmpty(bizName), "Biz name must not be empty."); AssertUtils.isFalse(StringUtils.isEmpty(bizVersion), "Biz version must not be empty."); Map bizCache = bizRegistration.get(bizName); if (bizCache != null) { Biz biz = bizCache.get(bizVersion); return biz != null && (biz.getBizState() == BizState.ACTIVATED); } return false; } @Override public void activeBiz(String bizName, String bizVersion) { AssertUtils.isFalse(StringUtils.isEmpty(bizName), "Biz name must not be empty."); AssertUtils.isFalse(StringUtils.isEmpty(bizVersion), "Biz version must not be empty."); Biz biz = getBiz(bizName, bizVersion); Biz activeBiz = getActiveBiz(bizName); if (biz != null && biz.getBizState() == BizState.DEACTIVATED) { if (activeBiz != null) { ((BizModel) activeBiz).setBizState(BizState.DEACTIVATED, StateChangeReason.SWITCHED, String.format("switch to new version %s", biz.getIdentity())); } String message = activeBiz == null ? "" : String.format("switch from old version: %s", activeBiz.getIdentity()); ((BizModel) biz).setBizState(BizState.ACTIVATED, StateChangeReason.SWITCHED, message); } } @Override public BizState getBizState(String bizName, String bizVersion) { AssertUtils.isFalse(StringUtils.isEmpty(bizName), "Biz name must not be empty."); AssertUtils.isFalse(StringUtils.isEmpty(bizVersion), "Biz version must not be empty."); Map bizCache = bizRegistration.get(bizName); if (bizCache != null) { Biz biz = bizCache.get(bizVersion); return biz != null ? biz.getBizState() : BizState.UNRESOLVED; } return BizState.UNRESOLVED; } @Override public BizState getBizState(String bizIdentity) { AssertUtils.isTrue(BizIdentityUtils.isValid(bizIdentity), "Format of Biz Identity is error."); String[] str = bizIdentity.split(Constants.STRING_COLON); return getBizState(str[0], str[1]); } @Override public boolean removeAndAddBiz(Biz addingBiz, Biz removingBiz) { Set>> bizEntrySet = bizRegistration.entrySet(); bizEntrySet.forEach(item -> { String bizName = item.getKey(); if (removingBiz.getBizName().equals(bizName)){ bizEntrySet.remove(item); return; } }); bizRegistration.putIfAbsent(addingBiz.getBizName(), new ConcurrentHashMap<>(16)); return bizRegistration.get(addingBiz.getBizName()).put(addingBiz.getBizVersion(), addingBiz) == null; } @Override public ConcurrentHashMap> getBizRegistration() { return bizRegistration; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/DefaultBizDeployer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.biz; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.service.ArkInject; import com.alipay.sofa.ark.spi.service.biz.BizDeployer; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; /** * Biz Deployer to deploy Biz * * @author qilong.zql * @since 0.4.0 */ public class DefaultBizDeployer implements BizDeployer { @ArkInject private BizManagerService bizManagerService; private String[] arguments; @Override public void init(String[] args) { this.arguments = args; } @Override public void deploy() { for (Biz biz : bizManagerService.getBizInOrder()) { if (isEmbedStaticBizAndIllegalState(biz)) { continue; } try { ArkLoggerFactory.getDefaultLogger().info( String.format("Begin to start biz: %s", biz.getBizName())); biz.start(arguments); ArkLoggerFactory.getDefaultLogger().info( String.format("Finish to start biz: %s", biz.getBizName())); } catch (Throwable e) { ArkLoggerFactory.getDefaultLogger().error( String.format("Start biz: %s meet error", biz.getBizName()), e); throw new ArkRuntimeException(e); } } } @Override public void unDeploy() { for (Biz biz : bizManagerService.getBizInOrder()) { try { ArkLoggerFactory.getDefaultLogger().info( String.format("Begin to stop biz: %s", biz.getBizName())); biz.stop(); ArkLoggerFactory.getDefaultLogger().info( String.format("Finish to stop biz: %s", biz.getBizName())); } catch (Throwable e) { ArkLoggerFactory.getDefaultLogger().error( String.format("stop biz: %s meet error", biz.getBizName()), e); throw new ArkRuntimeException(e); } } } public boolean isEmbedStaticBizAndIllegalState(Biz biz) { return ArkConfigs.isEmbedStaticBizEnable() && !BizState.RESOLVED.equals(biz.getBizState()); } @Override public String getDesc() { return String.format("{name=\'%s\', provider=\'%s\'}", "DefaultBizDeployer", "Ark Container"); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/classloader/AbstractClasspathClassLoader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.bootstrap.UseFastConnectionExceptionsEnumeration; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.model.PluginModel; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.exception.ArkLoaderException; import com.alipay.sofa.ark.loader.jar.Handler; import com.alipay.sofa.ark.loader.jar.JarUtils; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderService; import com.google.common.cache.Cache; import org.apache.commons.io.FileUtils; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; import java.net.URL; import java.net.URLClassLoader; import java.net.URLConnection; import java.security.AccessController; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.jar.JarFile; import static com.google.common.cache.CacheBuilder.newBuilder; import static java.util.concurrent.TimeUnit.SECONDS; /** * * Abstract Classpath ClassLoader, basic logic to load class/resource, sub class need to implement * * @author ruoshan * @since 0.1.0 */ public abstract class AbstractClasspathClassLoader extends URLClassLoader { protected static final String CLASS_RESOURCE_SUFFIX = ".class"; protected ClassLoaderService classloaderService = ArkServiceContainerHolder .getContainer() .getService( ClassLoaderService.class); protected Cache classCache; protected Cache> packageCache; protected Cache> urlResourceCache = newBuilder() .expireAfterWrite(10, SECONDS).build(); protected boolean exploded = false; static { ClassLoader.registerAsParallelCapable(); } public AbstractClasspathClassLoader(URL[] urls) { super(urls, null); classCache = newBuilder() .initialCapacity( ArkConfigs.getIntValue(Constants.ARK_CLASSLOADER_CACHE_CLASS_SIZE_INITIAL, 2500)) .maximumSize( ArkConfigs.getIntValue(Constants.ARK_CLASSLOADER_CACHE_CLASS_SIZE_MAX, 2500)) .concurrencyLevel( ArkConfigs.getIntValue(Constants.ARK_CLASSLOADER_CACHE_CONCURRENCY_LEVEL, 16)) .expireAfterWrite(30, SECONDS).recordStats().build(); packageCache = newBuilder() .initialCapacity( ArkConfigs.getIntValue(Constants.ARK_CLASSLOADER_CACHE_CLASS_SIZE_INITIAL, 2000)) .maximumSize( ArkConfigs.getIntValue(Constants.ARK_CLASSLOADER_CACHE_CLASS_SIZE_MAX, 2000)) .concurrencyLevel( ArkConfigs.getIntValue(Constants.ARK_CLASSLOADER_CACHE_CONCURRENCY_LEVEL, 16)) .expireAfterWrite(30, SECONDS).recordStats().build(); } public AbstractClasspathClassLoader(URL[] urls, boolean exploded) { this(urls); this.exploded = exploded; } @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { if (StringUtils.isEmpty(name)) { return null; } Handler.setUseFastConnectionExceptions(true); try { if (!exploded) { definePackageIfNecessary(name); } return loadClassWithCache(name, resolve); } finally { Handler.setUseFastConnectionExceptions(false); } } /** * Define a package before a {@code findClass} call is made. This is necessary to * ensure that the appropriate manifest for nested JARs is associated with the * package. * @param className the class name being found */ private void definePackageIfNecessary(String className) { int lastDot = className.lastIndexOf('.'); if (lastDot >= 0) { String packageName = className.substring(0, lastDot); Optional pkgInCache = packageCache.getIfPresent(packageName); // null means not cached, package haven't been defined yet, try to define it now if (pkgInCache == null) { try { definePackage(className, packageName); } catch (IllegalArgumentException ex) { // Tolerate race condition due to being parallel capable } finally { // cache define result Package pkgAfterDefined = super.getPackage(packageName); packageCache.put(packageName, pkgAfterDefined == null ? Optional.empty() : Optional.of(pkgAfterDefined)); } } } } private void definePackage(final String className, final String packageName) { try { AccessController.doPrivileged(new PrivilegedExceptionAction() { @Override public Object run() throws Exception { StringBuilder pen = new StringBuilder(packageName.length() + 10); StringBuilder cen = new StringBuilder(className.length() + 10); String packageEntryName = pen.append(packageName.replace('.', '/')).append("/") .toString(); String classEntryName = cen.append(className.replace('.', '/')) .append(".class").toString(); for (URL url : getURLs()) { try { URLConnection connection = url.openConnection(); if (connection instanceof JarURLConnection) { JarFile jarFile = ((JarURLConnection) connection).getJarFile(); if (jarFile.getEntry(classEntryName) != null && jarFile.getEntry(packageEntryName) != null && jarFile.getManifest() != null) { definePackage(packageName, jarFile.getManifest(), url); return null; } } } catch (IOException ex) { // Ignore } } return null; } }, AccessController.getContext()); } catch (java.security.PrivilegedActionException ex) { // Ignore } } @Override protected Package getPackage(String name) { Optional pkgInCache = packageCache.getIfPresent(name); if (pkgInCache != null && pkgInCache.isPresent()) { return pkgInCache.orElse(null); } Package pkg = super.getPackage(name); // don't cache null here because we may define pkg successfully later, // only cache null after define fail if (pkg != null) { packageCache.put(name, Optional.of(pkg)); } return pkg; } /** * cache load results of classes recently loaded * @param name * @param resolve * @return * @throws ArkLoaderException */ protected Class loadClassWithCache(String name, boolean resolve) throws ArkLoaderException { try { LoadClassResult resultInCache = classCache.get(name, () -> { LoadClassResult r = new LoadClassResult(); try { r.setClazz(loadClassInternal(name, resolve)); } catch (ArkLoaderException ex) { r.setEx(ex); } return r; }); if (resultInCache.getEx() != null) { throw resultInCache.getEx(); } return resultInCache.getClazz(); } catch (ExecutionException e) { throw new ArkLoaderException( String.format("[Ark Loader] unexpected exception when load class: %s", name), e.getCause()); } } /** * Real logic to load class,need to implement by Sub ClassLoader * @param name * @param resolve * @return * @throws ArkLoaderException */ abstract protected Class loadClassInternal(String name, boolean resolve) throws ArkLoaderException; @Override public URL getResource(String name) { Handler.setUseFastConnectionExceptions(true); Optional urlOptional = urlResourceCache.getIfPresent(name); try { if (urlOptional != null) { return urlOptional.orElse(null); } URL ret = preFindResource(name); if (ret != null) { return ret; } ret = getResourceInternal(name); URL url = ret != null ? ret : postFindResource(name); urlResourceCache.put(name, url != null ? Optional.of(url) : Optional.empty()); return url; } finally { Handler.setUseFastConnectionExceptions(false); } } /** * Real logic to get resource * @param name * @return */ protected URL getResourceInternal(String name) { // 1. find jdk resource URL url = getJdkResource(name); // 2. find export resource if (url == null) { url = getExportResource(name); } // 3. get .class resource if (url == null) { url = getClassResource(name); } // 4. get local resource if (url == null) { url = getLocalResource(name); } return url; } @Override public Enumeration getResources(String name) throws IOException { Handler.setUseFastConnectionExceptions(true); try { if (isDeclaredMode()) { List> enumerationList = new ArrayList<>(); // 1. get resources from ClassLoaderHook. enumerationList.add(preFindResources(name)); // 2. get jdk resources, plugin resources declared by the biz and resources in the biz. enumerationList.add(getResourcesInternal(name)); // 3. delegate master biz to get resources declared by the biz. enumerationList.add(postFindResources(name)); // unique urls return uniqueUrls(enumerationList, name); } else { Enumeration ret = preFindResources(name); if (ret != null && ret.hasMoreElements()) { return ret; } ret = getResourcesInternal(name); if (ret != null && ret.hasMoreElements()) { return ret; } ret = postFindResources(name); return ret != null ? ret : new CompoundEnumeration( (Enumeration[]) new Enumeration[] {}); } } finally { Handler.setUseFastConnectionExceptions(false); } } private Enumeration uniqueUrls(List> enumerationList, String resourceName) { // unique urls Set temp = new HashSet<>(); List uniqueUrls = new ArrayList<>(); for (Enumeration e : enumerationList) { while (e != null && e.hasMoreElements()) { URL resourceUrl = e.nextElement(); String filePath = resourceUrl.getFile().replace("file:", ""); if (filePath.endsWith(resourceName)) { filePath = filePath.substring(0, filePath.lastIndexOf(resourceName)); } String artifactId = JarUtils.parseArtifactId(filePath); if (artifactId == null) { uniqueUrls.add(resourceUrl); } else { if (!temp.contains(artifactId)) { uniqueUrls.add(resourceUrl); temp.add(artifactId); } } } } return Collections.enumeration(uniqueUrls); } /** * Real logic to get resources * @param name * @return * @throws IOException */ protected Enumeration getResourcesInternal(String name) throws IOException { List> enumerationList = new ArrayList<>(); // 1. find jdk resources enumerationList.add(getJdkResources(name)); // 2. find exported resources enumerationList.add(getExportResources(name)); // 3. find local resources enumerationList.add(getLocalResources(name)); return new CompoundEnumeration<>( enumerationList.toArray((Enumeration[]) new Enumeration[0])); } /** * Whether to find class that exported by other classloader * @param className class name * @return */ abstract boolean shouldFindExportedClass(String className); /** * Whether to find resource that exported by other classloader * @param resourceName * @return */ abstract boolean shouldFindExportedResource(String resourceName); private boolean isDeclaredMode() { return this instanceof BizClassLoader && ((BizClassLoader) this).checkDeclaredMode(); } /** * Load JDK class * @param name class name * @return */ protected Class resolveJDKClass(String name) { try { return classloaderService.getJDKClassLoader().loadClass(name); } catch (ClassNotFoundException e) { // ignore } return null; } /** * Load export class * @param name * @return */ protected Class resolveExportClass(String name) { if (!PluginModel.EXPORTMODE_OVERRIDE.equals(classloaderService.getExportMode(name))) { return doResolveExportClass(name); } else { ClassLoader classLoader = classloaderService.findExportClassLoader(name); URL url = classLoader.getResource(name.replace('.', '/') + ".class"); if (url != null) { String filePath = url.getFile().replaceFirst("file:", ""); try { byte[] bytes; if (filePath.contains(".jar")) { bytes = getClassBytesFromJar(filePath, name.replace('.', '/') + ".class"); } else { bytes = FileUtils.readFileToByteArray(new File(filePath)); } return defineClass(name, bytes, 0, bytes.length); } catch (Exception e) { ArkLoggerFactory.getDefaultLogger().warn( String.format("can't convert class to reLoad by bizClassLoader: %s", e.getMessage())); throw new RuntimeException(e); } } else { return null; } } } private byte[] getClassBytesFromJar(String jarFilePath, String className) throws IOException { try (com.alipay.sofa.ark.loader.jar.JarFile jarFile = JarUtils .getNestedRootJarFromJarLocation(jarFilePath); InputStream inputStream = jarFile.getInputStream(jarFile.getJarEntry(className))) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesRead; while ((bytesRead = inputStream.read(buffer, 0, bufferSize)) != -1) { byteArrayOutputStream.write(buffer, 0, bytesRead); } return byteArrayOutputStream.toByteArray(); } } private Class doResolveExportClass(String name) { if (shouldFindExportedClass(name)) { ClassLoader importClassLoader = null; if (this instanceof BizClassLoader) { importClassLoader = classloaderService.findExportClassLoaderByBiz( ((BizClassLoader) this).getBizModel(), name); } else if (this instanceof PluginClassLoader) { importClassLoader = classloaderService.findExportClassLoader(name); } if (importClassLoader != null) { try { Class clazz = importClassLoader.loadClass(name); if (clazz == null) { return null; } URL url = clazz.getProtectionDomain().getCodeSource().getLocation(); if (this instanceof BizClassLoader && ((BizClassLoader) this).getBizModel() != null) { BizModel bizModel = ((BizClassLoader) this).getBizModel(); if (url != null && bizModel.isDeclared(url, "")) { return clazz; } String classResourceName = name.replace('.', '/') + ".class"; Enumeration urls = importClassLoader.getResources(classResourceName); while (urls.hasMoreElements()) { URL resourceUrl = urls.nextElement(); if (resourceUrl != null && bizModel.isDeclared(resourceUrl, classResourceName)) { ArkLoggerFactory.getDefaultLogger().warn( String.format( "find class %s from %s in multiple dependencies.", name, resourceUrl.getFile())); return clazz; } } } else { return clazz; } } catch (ClassNotFoundException | NoClassDefFoundError | IOException e) { // just log when debug level if (ArkLoggerFactory.getDefaultLogger().isDebugEnabled()) { // log debug message ArkLoggerFactory.getDefaultLogger().debug( "Fail to load export class " + name, e); } } } } return null; } /** * Load ark spi class * @param name * @return */ protected Class resolveArkClass(String name) { if (classloaderService.isArkSpiClass(name) || classloaderService.isArkApiClass(name) || classloaderService.isArkLogClass(name) || classloaderService.isArkExceptionClass(name)) { try { return classloaderService.getArkClassLoader().loadClass(name); } catch (ClassNotFoundException e) { // ignore } } return null; } /** * Load classpath class * @param name * @return */ protected Class resolveLocalClass(String name) { try { return super.loadClass(name, false); } catch (ClassNotFoundException e) { // ignore } return null; } /** * Load Java Agent Class * @param name className * @return */ protected Class resolveJavaAgentClass(String name) { try { classloaderService.getAgentClassLoader().loadClass(name); return classloaderService.getSystemClassLoader().loadClass(name); } catch (ClassNotFoundException e) { // ignore } return null; } /** * Find export resource * @param resourceName * @return */ protected URL getExportResource(String resourceName) { if (shouldFindExportedResource(resourceName)) { List exportResourceClassLoadersInOrder = null; if (this instanceof BizClassLoader) { exportResourceClassLoadersInOrder = classloaderService .findExportResourceClassLoadersInOrderByBiz( ((BizClassLoader) this).getBizModel(), resourceName); } else if (this instanceof PluginClassLoader) { exportResourceClassLoadersInOrder = classloaderService .findExportResourceClassLoadersInOrder(resourceName); } if (exportResourceClassLoadersInOrder != null) { for (ClassLoader exportResourceClassLoader : exportResourceClassLoadersInOrder) { URL url = exportResourceClassLoader.getResource(resourceName); if (url != null && this instanceof BizClassLoader) { if (((BizClassLoader) (this)).getBizModel().isDeclared(url, resourceName)) { return url; } else { return null; } } return url; } } } return null; } /** * Find jdk dir resource * @param resourceName * @return */ protected URL getJdkResource(String resourceName) { return classloaderService.getJDKClassLoader().getResource(resourceName); } /** * Find .class resource * @param resourceName * @return */ protected URL getClassResource(String resourceName) { if (resourceName.endsWith(CLASS_RESOURCE_SUFFIX)) { String className = transformClassName(resourceName); if (resolveArkClass(className) != null) { return classloaderService.getArkClassLoader().getResource(resourceName); } if (shouldFindExportedClass(className)) { ClassLoader classLoader = classloaderService.findExportClassLoader(className); return classLoader == null ? null : classLoader.getResource(resourceName); } } return null; } /** * Find local resource * @param resourceName * @return */ protected URL getLocalResource(String resourceName) { return super.getResource(resourceName); } private String transformClassName(String name) { if (name.endsWith(CLASS_RESOURCE_SUFFIX)) { name = name.substring(0, name.length() - CLASS_RESOURCE_SUFFIX.length()); } return name.replace("/", "."); } /** * Find export resources * @param resourceName * @return */ @SuppressWarnings("unchecked") protected Enumeration getExportResources(String resourceName) throws IOException { if (shouldFindExportedResource(resourceName)) { List exportResourceClassLoadersInOrder = null; if (this instanceof BizClassLoader) { exportResourceClassLoadersInOrder = classloaderService .findExportResourceClassLoadersInOrderByBiz( ((BizClassLoader) this).getBizModel(), resourceName); } else if (this instanceof PluginClassLoader) { exportResourceClassLoadersInOrder = classloaderService .findExportResourceClassLoadersInOrder(resourceName); } if (exportResourceClassLoadersInOrder != null) { List> enumerationList = new ArrayList<>(); for (ClassLoader exportResourceClassLoader : exportResourceClassLoadersInOrder) { if (exportResourceClassLoader instanceof AbstractClasspathClassLoader) { enumerationList .add(((AbstractClasspathClassLoader) exportResourceClassLoader) .getLocalResources(resourceName)); } else { enumerationList.add(exportResourceClassLoader.getResources(resourceName)); } } Enumeration urls = new CompoundEnumeration<>( enumerationList.toArray((Enumeration[]) new Enumeration[0])); if (this instanceof BizClassLoader) { BizModel bizModel = ((BizClassLoader) this).getBizModel(); List matchedResourceUrls = new ArrayList<>(); while (urls.hasMoreElements()) { URL resourceUrl = urls.nextElement(); if (resourceUrl != null && bizModel.isDeclared(resourceUrl, resourceName)) { matchedResourceUrls.add(resourceUrl); } } return Collections.enumeration(matchedResourceUrls); } return urls; } } return Collections.emptyEnumeration(); } protected Enumeration getLocalResources(String resourceName) throws IOException { return new UseFastConnectionExceptionsEnumeration(super.getResources(resourceName)); } protected Enumeration getJdkResources(String resourceName) throws IOException { return new UseFastConnectionExceptionsEnumeration(classloaderService.getJDKClassLoader() .getResources(resourceName)); } public void clearCache() { classCache.cleanUp(); packageCache.cleanUp(); urlResourceCache.cleanUp(); } public void invalidAllCache() { classCache.invalidateAll(); packageCache.invalidateAll(); urlResourceCache.invalidateAll(); } /** * invoked before {@link #loadClass(String, boolean)} * * @param className * @return * @throws ArkLoaderException */ protected abstract Class preLoadClass(String className) throws ArkLoaderException; /** * invoked after {@link #loadClass(String, boolean)} * * @param className * @return * @throws ArkLoaderException */ protected abstract Class postLoadClass(String className) throws ArkLoaderException; /** * invoked before {@link #getResource(String)} * * @param resourceName * @return */ protected abstract URL preFindResource(String resourceName); /** * invoked after {@link #getResource(String)} * * @param resourceName * @return */ protected abstract URL postFindResource(String resourceName); /** * invoked before {@link #getResources(String)} * * @param resourceName * @return */ protected abstract Enumeration preFindResources(String resourceName) throws IOException; /** * invoked after {@link #getResources(String)} * * @param resourceName * @return */ protected abstract Enumeration postFindResources(String resourceName) throws IOException; public static class LoadClassResult { private ArkLoaderException ex; private Class clazz; public ArkLoaderException getEx() { return ex; } public void setEx(ArkLoaderException ex) { this.ex = ex; } public Class getClazz() { return clazz; } public void setClazz(Class clazz) { this.clazz = clazz; } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/classloader/BizClassLoader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.exception.ArkLoaderException; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderHook; import com.alipay.sofa.ark.spi.service.extension.ArkServiceLoader; import java.io.IOException; import java.net.URL; import java.security.ProtectionDomain; import java.util.Enumeration; import java.util.concurrent.atomic.AtomicBoolean; import static com.alipay.sofa.ark.spi.constant.Constants.BIZ_CLASS_LOADER_HOOK; import static com.alipay.sofa.ark.spi.constant.Constants.BIZ_CLASS_LOADER_HOOK_DIR; /** * Ark Biz ClassLoader * * @author ruoshan * @since 0.1.0 */ public class BizClassLoader extends AbstractClasspathClassLoader { private String bizIdentity; private BizManagerService bizManagerService = ArkServiceContainerHolder.getContainer() .getService(BizManagerService.class); private ClassLoaderHook bizClassLoaderHook; private AtomicBoolean isHookLoaded = new AtomicBoolean(false); private AtomicBoolean skipLoadHook = new AtomicBoolean(false); private final Object lock = new Object(); private BizModel bizModel; public void setBizModel(BizModel bizModel) { this.bizModel = bizModel; } public BizModel getBizModel() { return this.bizModel; } static { ClassLoader.registerAsParallelCapable(); } public BizClassLoader(String bizIdentity, URL[] urls) { super(urls); this.bizIdentity = bizIdentity; } public BizClassLoader(String bizIdentity, URL[] urls, boolean exploded) { this(bizIdentity, urls); this.exploded = exploded; } // support use biz classloader define app classloader class in org.springframework.cglib.core.ReflectUtils.defineClass public Class publicDefineClass(String name, byte[] b, ProtectionDomain protectionDomain) { return defineClass(name, b, 0, b.length, protectionDomain); } @Override protected Class loadClassInternal(String name, boolean resolve) throws ArkLoaderException { Class clazz = null; // 0. sun reflect related class throw exception directly if (classloaderService.isSunReflectClass(name)) { throw new ArkLoaderException( String .format( "[ArkBiz Loader] %s : can not load class: %s, this class can only be loaded by sun.reflect.DelegatingClassLoader", bizIdentity, name)); } // 1. findLoadedClass if (clazz == null) { clazz = findLoadedClass(name); } // 2. JDK related class if (clazz == null) { clazz = resolveJDKClass(name); } // 3. Ark Spi class if (clazz == null) { clazz = resolveArkClass(name); } // 4. pre find class if (clazz == null) { clazz = preLoadClass(name); } // 5. Plugin Export class if (clazz == null) { clazz = resolveExportClass(name); } // 6. Biz classpath class if (clazz == null) { clazz = resolveLocalClass(name); } // 7. Java Agent ClassLoader for agent problem if (clazz == null) { clazz = resolveJavaAgentClass(name); } // 8. post find class if (clazz == null) { clazz = postLoadClass(name); } if (clazz != null) { if (resolve) { super.resolveClass(clazz); } return clazz; } throw new ArkLoaderException(String.format("[ArkBiz Loader] %s : can not load class: %s", bizIdentity, name)); } @Override boolean shouldFindExportedClass(String className) { return !classloaderService.isDeniedImportClass(bizIdentity, className); } @Override boolean shouldFindExportedResource(String resourceName) { return !classloaderService.isDeniedImportResource(bizIdentity, resourceName); } public boolean checkDeclaredMode() { BizModel biz = this.getBizModel(); if (biz == null) { return false; } return biz.isDeclaredMode(); } private void loadBizClassLoaderHook() { if (!skipLoadHook.get()) { synchronized (lock) { if (isHookLoaded.compareAndSet(false, true)) { bizClassLoaderHook = ArkServiceLoader.loadExtensionFromArkBiz( ClassLoaderHook.class, BIZ_CLASS_LOADER_HOOK, bizIdentity); Biz masterBiz = ArkClient.getMasterBiz(); if (bizClassLoaderHook == null && masterBiz != null && !masterBiz.getIdentity().equals(bizIdentity)) { ClassLoader masterClassLoader = masterBiz.getBizClassLoader(); String defaultBizClassloaderHook = System .getProperty(BIZ_CLASS_LOADER_HOOK_DIR); if (!StringUtils.isEmpty(defaultBizClassloaderHook)) { try { bizClassLoaderHook = (ClassLoaderHook) masterClassLoader .loadClass(defaultBizClassloaderHook).newInstance(); } catch (Exception e) { throw new RuntimeException(String.format( "can not find master classloader hook: %s", defaultBizClassloaderHook), e); } } } skipLoadHook.set(true); } } } } @Override protected Class preLoadClass(String className) throws ArkLoaderException { try { loadBizClassLoaderHook(); return bizClassLoaderHook == null ? null : bizClassLoaderHook.preFindClass(className, classloaderService, bizManagerService.getBizByIdentity(bizIdentity)); } catch (Throwable throwable) { throw new ArkLoaderException(String.format( "Pre find class %s occurs an error via biz %s ClassLoaderHook: %s.", className, bizIdentity, bizClassLoaderHook), throwable); } } @Override protected Class postLoadClass(String className) throws ArkLoaderException { try { loadBizClassLoaderHook(); return bizClassLoaderHook == null ? null : bizClassLoaderHook.postFindClass(className, classloaderService, bizManagerService.getBizByIdentity(bizIdentity)); } catch (Throwable throwable) { throw new ArkLoaderException(String.format( "Post find class %s occurs an error via biz %s ClassLoaderHook: %s.", className, bizIdentity, bizClassLoaderHook), throwable); } } @Override protected URL preFindResource(String resourceName) { loadBizClassLoaderHook(); return bizClassLoaderHook == null ? null : bizClassLoaderHook.preFindResource(resourceName, classloaderService, bizManagerService.getBizByIdentity(bizIdentity)); } @Override protected URL postFindResource(String resourceName) { loadBizClassLoaderHook(); return bizClassLoaderHook == null ? null : bizClassLoaderHook.postFindResource( resourceName, classloaderService, bizManagerService.getBizByIdentity(bizIdentity)); } @Override protected Enumeration preFindResources(String resourceName) throws IOException { loadBizClassLoaderHook(); return bizClassLoaderHook == null ? null : bizClassLoaderHook.preFindResources( resourceName, classloaderService, bizManagerService.getBizByIdentity(bizIdentity)); } @Override protected Enumeration postFindResources(String resourceName) throws IOException { loadBizClassLoaderHook(); return bizClassLoaderHook == null ? null : bizClassLoaderHook.postFindResources( resourceName, classloaderService, bizManagerService.getBizByIdentity(bizIdentity)); } /** * Getter method for property bizIdentity. * * @return property value of bizIdentity */ public String getBizIdentity() { return bizIdentity; } public void setBizIdentity(String bizIdentity) { this.bizIdentity = bizIdentity; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/classloader/ClassLoaderServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.bootstrap.AgentClassLoader; import com.alipay.sofa.ark.common.log.ArkLogger; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.common.util.ClassUtils; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.model.PluginModel; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import com.google.inject.Inject; import com.google.inject.Singleton; import java.io.File; import java.net.URL; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; /** * ClassLoader Service Implementation * * @author ruoshan * @since 0.1.0 */ @Singleton public class ClassLoaderServiceImpl implements ClassLoaderService { private static final String ARK_SPI_PACKAGES = "com.alipay.sofa.ark.spi"; private static final String ARK_API_PACKAGES = "com.alipay.sofa.ark.api"; private static final String ARK_LOG_PACKAGES = "com.alipay.sofa.ark.common.log"; private static final String ARK_EXCEPTION_PACKAGES = "com.alipay.sofa.ark.exception"; private static final List SUN_REFLECT_GENERATED_ACCESSOR = new ArrayList<>(); /* export class and classloader relationship cache */ private ConcurrentHashMap exportClassAndClassLoaderMap = new ConcurrentHashMap<>(); private ConcurrentHashMap exportNodeAndClassLoaderMap = new ConcurrentHashMap<>(); private ConcurrentHashMap exportStemAndClassLoaderMap = new ConcurrentHashMap<>(); /* export cache and classloader relationship cache */ private ConcurrentHashMap> exportResourceAndClassLoaderMap = new ConcurrentHashMap<>(); private ConcurrentHashMap> exportPrefixStemResourceAndClassLoaderMap = new ConcurrentHashMap<>(); private ConcurrentHashMap> exportSuffixStemResourceAndClassLoaderMap = new ConcurrentHashMap<>(); private ClassLoader jdkClassLoader; private ClassLoader arkClassLoader; private ClassLoader systemClassLoader; private ClassLoader agentClassLoader; @Inject private PluginManagerService pluginManagerService; @Inject private BizManagerService bizManagerService; static { SUN_REFLECT_GENERATED_ACCESSOR.add("sun.reflect.GeneratedMethodAccessor"); SUN_REFLECT_GENERATED_ACCESSOR.add("sun.reflect.GeneratedConstructorAccessor"); SUN_REFLECT_GENERATED_ACCESSOR.add("sun.reflect.GeneratedSerializationConstructorAccessor"); } @Override public boolean isSunReflectClass(String className) { for (String sunAccessor : SUN_REFLECT_GENERATED_ACCESSOR) { if (className.startsWith(sunAccessor)) { return true; } } return false; } @Override public boolean isArkSpiClass(String className) { return className.startsWith(ARK_SPI_PACKAGES); } @Override public boolean isArkApiClass(String className) { return className.startsWith(ARK_API_PACKAGES); } @Override public boolean isArkLogClass(String className) { return className.startsWith(ARK_LOG_PACKAGES); } @Override public boolean isArkExceptionClass(String className) { return className.startsWith(ARK_EXCEPTION_PACKAGES); } @Override public void prepareExportClassAndResourceCache() { for (Plugin plugin : pluginManagerService.getPluginsInOrder()) { for (String exportIndex : plugin.getExportPackageNodes()) { exportNodeAndClassLoaderMap.putIfAbsent(exportIndex, plugin); } for (String exportIndex : plugin.getExportPackageStems()) { exportStemAndClassLoaderMap.putIfAbsent(exportIndex, plugin); } for (String exportIndex : plugin.getExportClasses()) { exportClassAndClassLoaderMap.putIfAbsent(exportIndex, plugin); } for (String resource : plugin.getExportResources()) { exportResourceAndClassLoaderMap.putIfAbsent(resource, new LinkedList<>()); exportResourceAndClassLoaderMap.get(resource).add(plugin); } for (String resource : plugin.getExportPrefixResourceStems()) { exportPrefixStemResourceAndClassLoaderMap.putIfAbsent(resource, new LinkedList<>()); exportPrefixStemResourceAndClassLoaderMap.get(resource).add(plugin); } for (String resource : plugin.getExportSuffixResourceStems()) { exportSuffixStemResourceAndClassLoaderMap.putIfAbsent(resource, new LinkedList<>()); exportSuffixStemResourceAndClassLoaderMap.get(resource).add(plugin); } } } @Override public boolean isClassInImport(String pluginName, String className) { Plugin plugin = pluginManagerService.getPluginByName(pluginName); AssertUtils.assertNotNull(plugin, "plugin: " + pluginName + " is null"); for (String importName : plugin.getImportClasses()) { if (className.equals(importName)) { return true; } } String pkg = ClassUtils.getPackageName(className); for (String pattern : plugin.getImportPackageNodes()) { if (pkg.equals(pattern)) { return true; } } for (String pattern : plugin.getImportPackageStems()) { if (pkg.startsWith(pattern)) { return true; } } return false; } public String getExportMode(String className) { Plugin plugin = findExportPlugin(className); if (plugin == null) { return PluginModel.EXPORTMODE_UNKNOWN; } return plugin.getExportMode(); } @Override public ClassLoader findExportClassLoader(String className) { Plugin plugin = findExportPlugin(className); if (plugin != null) { return plugin.getPluginClassLoader(); } else { return null; } } @Override public ClassLoader findExportClassLoaderByBiz(Biz biz, String className) { BizModel bizModel = (BizModel) biz; Plugin plugin = bizModel.getExportClassAndClassLoaderMap().get(className); String packageName = ClassUtils.getPackageName(className); if (plugin == null) { plugin = bizModel.getExportNodeAndClassLoaderMap().get(packageName); } while (!Constants.DEFAULT_PACKAGE.equals(packageName) && plugin == null) { plugin = bizModel.getExportStemAndClassLoaderMap().get(packageName); packageName = ClassUtils.getPackageName(packageName); } if (plugin != null) { return plugin.getPluginClassLoader(); } else { return null; } } @Override public Plugin findExportPlugin(String className) { Plugin plugin = exportClassAndClassLoaderMap.get(className); String packageName = ClassUtils.getPackageName(className); if (plugin == null) { plugin = exportNodeAndClassLoaderMap.get(packageName); } while (!Constants.DEFAULT_PACKAGE.equals(packageName) && plugin == null) { plugin = exportStemAndClassLoaderMap.get(packageName); packageName = ClassUtils.getPackageName(packageName); } return plugin; } @Override public boolean isResourceInImport(String pluginName, String resourceName) { Plugin plugin = pluginManagerService.getPluginByName(pluginName); AssertUtils.assertNotNull(plugin, "plugin: " + pluginName + " is null"); for (String importResource : plugin.getImportResources()) { if (importResource.equals(resourceName)) { return true; } } for (String importResource : plugin.getImportPrefixResourceStems()) { if (resourceName.startsWith(importResource)) { return true; } } for (String importResource : plugin.getImportSuffixResourceStems()) { if (resourceName.endsWith(importResource)) { return true; } } return false; } @Override public List findExportResourceClassLoadersInOrder(String resourceName) { List plugins = findExportResourcePluginsInOrder(resourceName); if (plugins != null) { return plugins.stream().map(Plugin::getPluginClassLoader).collect(Collectors.toList()); } else { return null; } } @Override public List findExportResourceClassLoadersInOrderByBiz(Biz biz, String resourceName) { BizModel bizModel = (BizModel) biz; List plugins = findExportResourcePluginsInOrderByBiz(bizModel, resourceName); if (plugins != null) { return plugins.stream().map(Plugin::getPluginClassLoader).collect(Collectors.toList()); } else { return null; } } private List findExportResourcePluginsInOrderByBiz(BizModel bizModel, String resourceName) { if (bizModel.getExportResourceAndClassLoaderMap().containsKey(resourceName)) { return bizModel.getExportResourceAndClassLoaderMap().get(resourceName); } for (String stemResource : bizModel.getExportPrefixStemResourceAndClassLoaderMap().keySet()) { if (resourceName.startsWith(stemResource)) { return bizModel.getExportPrefixStemResourceAndClassLoaderMap().get(stemResource); } } for (String stemResource : bizModel.getExportSuffixStemResourceAndClassLoaderMap().keySet()) { if (resourceName.endsWith(stemResource)) { return bizModel.getExportSuffixStemResourceAndClassLoaderMap().get(stemResource); } } return null; } private List findExportResourcePluginsInOrder(String resourceName) { if (exportResourceAndClassLoaderMap.containsKey(resourceName)) { return exportResourceAndClassLoaderMap.get(resourceName); } for (String stemResource : exportPrefixStemResourceAndClassLoaderMap.keySet()) { if (resourceName.startsWith(stemResource)) { return exportPrefixStemResourceAndClassLoaderMap.get(stemResource); } } for (String stemResource : exportSuffixStemResourceAndClassLoaderMap.keySet()) { if (resourceName.endsWith(stemResource)) { return exportSuffixStemResourceAndClassLoaderMap.get(stemResource); } } return null; } @Override public ClassLoader getJDKClassLoader() { return jdkClassLoader; } @Override public ClassLoader getArkClassLoader() { return arkClassLoader; } @Override public ClassLoader getSystemClassLoader() { return systemClassLoader; } @Override public ClassLoader getAgentClassLoader() { return agentClassLoader; } @Override public ClassLoader getBizClassLoader(String bizIdentity) { Biz biz = bizManagerService.getBizByIdentity(bizIdentity); return biz == null ? null : biz.getBizClassLoader(); } @Override public ClassLoader getMasterBizClassLoader() { Biz biz = ArkClient.getMasterBiz(); return biz == null ? null : biz.getBizClassLoader(); } @Override public ClassLoader getPluginClassLoader(String pluginName) { Plugin plugin = pluginManagerService.getPluginByName(pluginName); return plugin == null ? null : plugin.getPluginClassLoader(); } @Override public void init() throws ArkRuntimeException { arkClassLoader = this.getClass().getClassLoader(); systemClassLoader = ClassLoader.getSystemClassLoader(); agentClassLoader = createAgentClassLoader(); ClassLoader extClassLoader = systemClassLoader; while (extClassLoader.getParent() != null) { extClassLoader = extClassLoader.getParent(); } List jdkUrls = new ArrayList<>(); try { String javaHome = System.getProperty("java.home").replace(File.separator + "jre", ""); URL[] urls = ClassLoaderUtils.getURLs(systemClassLoader); for (URL url : urls) { if (url.getPath().startsWith(javaHome)) { if (ArkLoggerFactory.getDefaultLogger().isDebugEnabled()) { ArkLoggerFactory.getDefaultLogger().debug( String.format("Find JDK Url: %s", url)); } jdkUrls.add(url); } } } catch (Throwable e) { ArkLoggerFactory.getDefaultLogger().warn("Meet exception when parse JDK urls", e); } jdkClassLoader = new JDKDelegateClassLoader(jdkUrls.toArray(new URL[0]), extClassLoader); } @Override public void dispose() throws ArkRuntimeException { } private ClassLoader createAgentClassLoader() throws ArkRuntimeException { return new AgentClassLoader(ClassLoaderUtils.getAgentClassPath(), null); } @Override public boolean isDeniedImportClass(String bizIdentity, String className) { Biz biz = bizManagerService.getBizByIdentity(bizIdentity); if (biz == null) { return false; } for (String pattern : biz.getDenyImportClasses()) { if (pattern.equals(className)) { return true; } } String pkg = ClassUtils.getPackageName(className); for (String pattern : biz.getDenyImportPackageNodes()) { if (pkg.equals(pattern)) { return true; } } for (String pattern : biz.getDenyImportPackageStems()) { if (pkg.startsWith(pattern)) { return true; } } return false; } @Override public boolean isDeniedImportResource(String bizIdentity, String resourceName) { Biz biz = bizManagerService.getBizByIdentity(bizIdentity); if (biz == null) { return false; } for (String resource : biz.getDenyImportResources()) { if (resource.equals(resourceName)) { return true; } } for (String resource : biz.getDenyPrefixImportResourceStems()) { if (resourceName.startsWith(resource)) { return true; } } for (String resource : biz.getDenySuffixImportResourceStems()) { if (resourceName.endsWith(resource)) { return true; } } return false; } @Override public int getPriority() { return DEFAULT_PRECEDENCE; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/classloader/CompoundEnumeration.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader; import java.util.Enumeration; import java.util.NoSuchElementException; /* * A utility class that will enumerate over an array of enumerations. */ public final class CompoundEnumeration implements Enumeration { private final Enumeration[] enums; private int index; public CompoundEnumeration(Enumeration[] enums) { this.enums = enums; } private boolean next() { while (index < enums.length) { if (enums[index] != null && enums[index].hasMoreElements()) { return true; } index++; } return false; } public boolean hasMoreElements() { return next(); } public E nextElement() { if (!next()) { throw new NoSuchElementException(); } return enums[index].nextElement(); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/classloader/JDKDelegateClassLoader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader; import java.net.URL; import java.net.URLClassLoader; /** * JDK Delegate ClassLoader, parent is excClassLoader, urls are jdk related path on SystemClassLoader * * @author ruoshan * @since 0.1.0 */ public class JDKDelegateClassLoader extends URLClassLoader { static { ClassLoader.registerAsParallelCapable(); } public JDKDelegateClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/classloader/PluginClassLoader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.exception.ArkLoaderException; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderHook; import com.alipay.sofa.ark.spi.service.extension.ArkServiceLoader; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import java.io.IOException; import java.net.URL; import java.util.Enumeration; import java.util.concurrent.atomic.AtomicBoolean; import static com.alipay.sofa.ark.spi.constant.Constants.PLUGIN_CLASS_LOADER_HOOK; /** * Ark Plugin ClassLoader * * @author ruoshan * @since 0.1.0 */ public class PluginClassLoader extends AbstractClasspathClassLoader { private String pluginName; private ClassLoaderHook pluginClassLoaderHook; private AtomicBoolean isHookLoaded = new AtomicBoolean(false); private AtomicBoolean skipLoadHook = new AtomicBoolean(false); private PluginManagerService pluginManagerService = ArkServiceContainerHolder .getContainer() .getService(PluginManagerService.class); private final Object lock = new Object(); static { ClassLoader.registerAsParallelCapable(); } public PluginClassLoader(String pluginName, URL[] urls) { super(urls); this.pluginName = pluginName; } public String getPluginName() { return pluginName; } @Override protected Class loadClassInternal(String name, boolean resolve) throws ArkLoaderException { Class clazz = null; // 0. sun reflect related class throw exception directly if (classloaderService.isSunReflectClass(name)) { throw new ArkLoaderException( String .format( "[ArkPlugin Loader] %s : can not load class: %s, this class can only be loaded by sun.reflect.DelegatingClassLoader", pluginName, name)); } // 1. findLoadedClass if (clazz == null) { clazz = findLoadedClass(name); } // 2. JDK related class if (clazz == null) { clazz = resolveJDKClass(name); } // 3. Ark Spi class if (clazz == null) { clazz = resolveArkClass(name); } // 4. pre find class if (clazz == null) { clazz = preLoadClass(name); } // 5. Import class export by other plugins if (clazz == null) { clazz = resolveExportClass(name); } // 6. Plugin classpath class if (clazz == null) { clazz = resolveLocalClass(name); } // 7. Java Agent ClassLoader for agent problem if (clazz == null) { clazz = resolveJavaAgentClass(name); } // 8. Post find class if (clazz == null) { clazz = postLoadClass(name); } if (clazz != null) { if (resolve) { super.resolveClass(clazz); } return clazz; } throw new ArkLoaderException(String.format( "[ArkPlugin Loader] %s : can not load class: %s", pluginName, name)); } @Override boolean shouldFindExportedClass(String className) { return classloaderService.isClassInImport(pluginName, className); } @Override boolean shouldFindExportedResource(String resourceName) { return classloaderService.isResourceInImport(pluginName, resourceName); } private void loadPluginClassLoaderHook() { if (!skipLoadHook.get()) { synchronized (lock) { if (isHookLoaded.compareAndSet(false, true)) { pluginClassLoaderHook = ArkServiceLoader.loadExtensionFromArkPlugin( ClassLoaderHook.class, PLUGIN_CLASS_LOADER_HOOK, pluginName); skipLoadHook.set(true); } } } } @Override protected Class preLoadClass(String className) throws ArkLoaderException { try { loadPluginClassLoaderHook(); return pluginClassLoaderHook == null ? null : pluginClassLoaderHook.preFindClass( className, classloaderService, pluginManagerService.getPluginByName(pluginName)); } catch (Throwable throwable) { throw new ArkLoaderException(String.format( "Pre find class %s occurs an error via plugin ClassLoaderHook: %s.", className, pluginClassLoaderHook), throwable); } } @Override protected Class postLoadClass(String className) throws ArkLoaderException { try { loadPluginClassLoaderHook(); return pluginClassLoaderHook == null ? null : pluginClassLoaderHook.postFindClass( className, classloaderService, pluginManagerService.getPluginByName(pluginName)); } catch (Throwable throwable) { throw new ArkLoaderException(String.format( "Post find class %s occurs an error via plugin ClassLoaderHook: %s.", className, pluginClassLoaderHook), throwable); } } @Override protected URL preFindResource(String resourceName) { loadPluginClassLoaderHook(); return pluginClassLoaderHook == null ? null : pluginClassLoaderHook.preFindResource( resourceName, classloaderService, pluginManagerService.getPluginByName(pluginName)); } @Override protected URL postFindResource(String resourceName) { loadPluginClassLoaderHook(); return pluginClassLoaderHook == null ? null : pluginClassLoaderHook.postFindResource( resourceName, classloaderService, pluginManagerService.getPluginByName(pluginName)); } @Override protected Enumeration preFindResources(String resourceName) throws IOException { loadPluginClassLoaderHook(); return pluginClassLoaderHook == null ? null : pluginClassLoaderHook.preFindResources( resourceName, classloaderService, pluginManagerService.getPluginByName(pluginName)); } @Override protected Enumeration postFindResources(String resourceName) throws IOException { loadPluginClassLoaderHook(); return pluginClassLoaderHook == null ? null : pluginClassLoaderHook.postFindResources( resourceName, classloaderService, pluginManagerService.getPluginByName(pluginName)); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/event/EventAdminServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.event; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.OrderComparator; import com.alipay.sofa.ark.spi.event.ArkEvent; import com.alipay.sofa.ark.spi.registry.ServiceReference; import com.alipay.sofa.ark.spi.service.PriorityOrdered; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import com.alipay.sofa.ark.spi.service.event.EventHandler; import com.alipay.sofa.ark.spi.service.registry.RegistryService; import com.google.inject.Inject; import com.google.inject.Singleton; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArraySet; /** * @author qilong.zql * @since 0.4.0 */ @Singleton public class EventAdminServiceImpl implements EventAdminService, EventHandler { private final static ConcurrentMap> SUBSCRIBER_MAP = new ConcurrentHashMap<>(); @Inject private RegistryService registryService; public EventAdminServiceImpl() { register(this); } @Override public void sendEvent(ArkEvent event) { List eventHandlers = new ArrayList<>(); for (CopyOnWriteArraySet values : SUBSCRIBER_MAP.values()) { eventHandlers.addAll(values); } for (ServiceReference eventHandler : registryService.referenceServices( EventHandler.class, null)) { eventHandlers.add(eventHandler.getService()); } Collections.sort(eventHandlers, new OrderComparator()); for (EventHandler eventHandler : eventHandlers) { if (isSupportEventType(eventHandler, event)) { eventHandler.handleEvent(event); } } } @Override public void register(EventHandler eventHandler) { CopyOnWriteArraySet set = SUBSCRIBER_MAP.get(eventHandler.getClass() .getClassLoader()); if (set == null) { set = new CopyOnWriteArraySet<>(); CopyOnWriteArraySet old = SUBSCRIBER_MAP.putIfAbsent(eventHandler .getClass().getClassLoader(), set); if (old != null) { set = old; } } set.add(eventHandler); ArkLoggerFactory.getDefaultLogger().debug( String.format("Register event handler: %s.", eventHandler)); } @Override public void unRegister(EventHandler eventHandler) { CopyOnWriteArraySet set = SUBSCRIBER_MAP.get(eventHandler.getClass() .getClassLoader()); if (set != null) { set.remove(eventHandler); ArkLoggerFactory.getDefaultLogger().debug( String.format("Unregister event handler: %s.", eventHandler)); } } @Override public void unRegister(ClassLoader classLoader) { SUBSCRIBER_MAP.remove(classLoader); ArkLoggerFactory.getDefaultLogger().debug( String.format("Unregister event handler of classLoader: %s.", classLoader)); } @Override public void handleEvent(ArkEvent event) { } @Override public int getPriority() { return PriorityOrdered.LOWEST_PRECEDENCE; } private boolean isSupportEventType(EventHandler eventHandler, ArkEvent event) { boolean isSupport = false; try { Class aClass = eventHandler.getClass(); // get current class's interface type Type[] types = aClass.getGenericInterfaces(); if (types != null) { // traverse types for (Type type : types) { if (!checkEventHandlerType(type)) { continue; } if (type instanceof ParameterizedType) { // a generic type is specified, the current type and its subclasses will be processed Type[] actualTypeArguments = ((ParameterizedType) type) .getActualTypeArguments(); if (actualTypeArguments.length == 1) { if (Class.forName(actualTypeArguments[0].getTypeName()) .isAssignableFrom(event.getClass())) { isSupport = true; break; } } } else { // no generic type is specified, ArkEvent and its subclasses will handle if (ArkEvent.class.isAssignableFrom(event.getClass())) { isSupport = true; break; } } } } } catch (Throwable t) { // ignore } return isSupport; } private boolean checkEventHandlerType(Type type) { if (type.getTypeName().equals(EventHandler.class.getTypeName())) { return true; } if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; return parameterizedType.getRawType().getTypeName() .equals(EventHandler.class.getTypeName()); } return false; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/extension/ExtensionLoaderServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.extension; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.extension.Extensible; import com.alipay.sofa.ark.spi.service.extension.Extension; import com.alipay.sofa.ark.spi.service.extension.ExtensionClass; import com.alipay.sofa.ark.spi.service.extension.ExtensionLoaderService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import javax.inject.Singleton; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import static com.alipay.sofa.ark.spi.constant.Constants.EXTENSION_FILE_DIR; /** * @author qilong.zql * @since 0.6.0 */ @Singleton public class ExtensionLoaderServiceImpl implements ExtensionLoaderService { private PluginManagerService pluginManagerService = ArkServiceContainerHolder.getContainer() .getService(PluginManagerService.class); private BizManagerService bizManagerService = ArkServiceContainerHolder.getContainer() .getService(BizManagerService.class); @Override public T getExtensionContributorFromArkPlugin(Class interfaceType, String extensionName, String pluginName) { AssertUtils.assertNotNull(interfaceType, "interfaceType can't be null."); AssertUtils.assertNotNull(extensionName, "extensionName can't be null."); AssertUtils.assertNotNull(pluginName, "pluginName can't be null."); Plugin plugin = pluginManagerService.getPluginByName(pluginName); AssertUtils.assertNotNull(plugin, "plugin: " + pluginName + " is null"); return getExtensionContributor(interfaceType, extensionName, plugin, plugin.getPluginClassLoader()); } @Override public T getExtensionContributorFromArkBiz(Class interfaceType, String extensionName, String bizIdentity) { AssertUtils.assertNotNull(interfaceType, "interfaceType can't be null."); AssertUtils.assertNotNull(extensionName, "extensionName can't be null."); AssertUtils.assertNotNull(bizIdentity, "bizIdentity can't be null."); Biz biz = bizManagerService.getBizByIdentity(bizIdentity); AssertUtils.assertNotNull(biz, "biz: " + bizIdentity + " is null"); return getExtensionContributor(interfaceType, extensionName, biz, biz.getBizClassLoader()); } @Override public List getExtensionContributorsFromArkBiz(Class interfaceType, String bizIdentity) { AssertUtils.assertNotNull(interfaceType, "interfaceType can't be null."); AssertUtils.assertNotNull(bizIdentity, "bizIdentity can't be null."); Biz biz = bizManagerService.getBizByIdentity(bizIdentity); AssertUtils.assertNotNull(biz, "biz: " + bizIdentity + " is null"); return getExtensionContributors(interfaceType, biz, biz.getBizClassLoader()); } public T getExtensionContributor(Class interfaceType, String extensionName, L location, ClassLoader resourceLoader) { ExtensionClass extensionClass = null; try { Set> extensionClassSet = loadExtension(interfaceType, extensionName, location, resourceLoader); for (ExtensionClass extensionClazz : extensionClassSet) { if (extensionClass == null || extensionClass.getPriority() > extensionClazz.getPriority()) { extensionClass = extensionClazz; } } } catch (Throwable throwable) { ArkLoggerFactory.getDefaultLogger() .error("Loading extension of interfaceType: {} occurs error {}.", interfaceType, throwable); throw new ArkRuntimeException(throwable); } return extensionClass == null ? null : extensionClass.getObject(); } public List getExtensionContributors(Class interfaceType, L location, ClassLoader resourceLoader) { try { Set> extensionClassSet = loadExtensions(interfaceType, location, resourceLoader); Map nameToExtensionClass = new HashMap<>(); for (ExtensionClass extensionClazz : extensionClassSet) { if(extensionClazz!=null){ String extensionName = extensionClazz.getExtension().value(); nameToExtensionClass.putIfAbsent(extensionName,extensionClazz); if(extensionClazz.getPriority() > nameToExtensionClass.get(extensionName).getPriority()){ nameToExtensionClass.put(extensionName,extensionClazz); } } } return (List)nameToExtensionClass.values().stream().map(it -> it.getObject()).collect(Collectors.toList()); } catch (Throwable throwable) { ArkLoggerFactory.getDefaultLogger() .error("Loading extension of interfaceType: {} occurs error {}.", interfaceType, throwable); throw new ArkRuntimeException(throwable); } } private Set> loadExtension(Class interfaceType, String extensionName, L location, ClassLoader resourceLoader) throws Throwable { Set> extensionClassSet = loadExtensions(interfaceType,location,resourceLoader); return extensionClassSet.stream().filter(it -> extensionName.equals(it.getExtension().value())).collect(Collectors.toSet()); } private Set> loadExtensions(Class interfaceType, L location, ClassLoader resourceLoader) throws Throwable { BufferedReader reader = null; try { Set> extensionClassSet = new HashSet<>(); Extensible extensible = interfaceType.getAnnotation(Extensible.class); if (extensible == null) { throw new ArkRuntimeException(String.format( "Extensible class %s is not annotated by %s.", interfaceType, Extensible.class)); } String fileName = interfaceType.getCanonicalName(); if (!StringUtils.isEmpty(extensible.file())) { fileName = extensible.file(); } Enumeration enumeration = resourceLoader.getResources(EXTENSION_FILE_DIR + fileName); while (enumeration.hasMoreElements()) { URL url = enumeration.nextElement(); if (ArkLoggerFactory.getDefaultLogger().isDebugEnabled()) { ArkLoggerFactory.getDefaultLogger().debug( "Loading extension of extensible: {} from location: {} and file: {}", interfaceType, location, url); } reader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8")); String line; while ((line = reader.readLine()) != null) { ExtensionClass extensionClass = new ExtensionClass<>(); extensionClass.setDefinedLocation(location); extensionClass.setExtensible(extensible); extensionClass.setInterfaceClass(interfaceType); Class implementClass = null; String clazzName = line.trim(); try { implementClass = resourceLoader.loadClass(clazzName); } catch (Exception e) { if (ArkConfigs.isEmbedEnable() && resourceLoader != ArkClient.getMasterBiz().getBizClassLoader()) { implementClass = ArkClient.getMasterBiz().getBizClassLoader() .loadClass(clazzName); } else { throw e; } } if (!interfaceType.isAssignableFrom(implementClass)) { throw new ArkRuntimeException(String.format( "Extension implementation class %s is not type of %s.", implementClass.getCanonicalName(), interfaceType.getCanonicalName())); } Extension extension = implementClass.getAnnotation(Extension.class); if (extension == null) { throw new ArkRuntimeException(String.format( "Extension implementation class %s is not annotated by %s.", implementClass, Extension.class)); } extensionClass.setExtension(extension); extensionClass.setImplementClass((Class) implementClass); extensionClassSet.add(extensionClass); } } return extensionClassSet; } catch (Throwable throwable) { ArkLoggerFactory.getDefaultLogger().error( "Loading extension files from {} occurs an error {}.", location, throwable); throw throwable; } finally { if (reader != null) { reader.close(); } } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/injection/InjectionServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.injection; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.ReflectionUtils; import com.alipay.sofa.ark.common.util.ReflectionUtils.FieldCallback; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.registry.ServiceReference; import com.alipay.sofa.ark.spi.service.ArkInject; import com.alipay.sofa.ark.spi.service.injection.InjectionService; import com.alipay.sofa.ark.spi.service.registry.RegistryService; import com.google.inject.Inject; import com.google.inject.Singleton; import java.lang.reflect.Field; /** * {@link InjectionService} * * @author qilong.zql * @since 0.4.0 */ @Singleton public class InjectionServiceImpl implements InjectionService { @Inject private RegistryService registryService; @Override public void inject(final ServiceReference reference) { inject(reference.getService(), reference.toString()); } @Override public void inject(final Object object) { inject(object, object.getClass().getName()); } private void inject(final Object instance, final String type) { ReflectionUtils.doWithFields(instance.getClass(), new FieldCallback() { @Override public void doWith(Field field) throws ArkRuntimeException { ArkInject arkInject = field.getAnnotation(ArkInject.class); if (arkInject == null) { return; } Class serviceType = arkInject.interfaceType() == void.class ? field.getType() : arkInject.interfaceType(); Object value = getService(serviceType, arkInject.uniqueId()); if (value == null) { ArkLoggerFactory.getDefaultLogger().warn( String.format("Inject {field= %s} of {service= %s} fail!", field.getName(), type)); return; } ReflectionUtils.makeAccessible(field); try { field.set(instance, value); ArkLoggerFactory.getDefaultLogger().info( String.format("Inject {field= %s} of {service= %s} success!", field.getName(), type)); } catch (Throwable throwable) { throw new ArkRuntimeException(throwable); } } }); } private Object getService(Class serviceType, String uniqueId) { ServiceReference serviceReference = registryService.referenceService(serviceType, uniqueId); return serviceReference == null ? null : serviceReference.getService(); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/plugin/PluginCommandProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.plugin; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.ArkInject; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import com.alipay.sofa.ark.spi.service.session.CommandProvider; import java.net.URL; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; /** * @author qilong.zql * @since 0.6.0 */ public class PluginCommandProvider implements CommandProvider { @ArkInject private PluginManagerService pluginManagerService; @Override public String getHelp() { return HELP_MESSAGE; } @Override public String handleCommand(String command) { return new PluginCommand(command).process(); } @Override public boolean validate(String command) { return new PluginCommand(command).isValidate(); } private static final String HELP_MESSAGE = "Plugin Command Tips:\n" + " USAGE: plugin [option...] [pluginName...]\n" + " SAMPLE: plugin -m plugin-A plugin-B\n" + " -h Shows the help message.\n" + " -a Shows all plugin name.\n" + " -m Shows the meta info of specified pluginName.\n" + " -s Shows the service info of specified pluginName.\n" + " -d Shows the detail info of specified pluginName.\n"; class PluginCommand { private boolean isValidate; private Set options = new HashSet<>(); private Set parameters = new HashSet<>(); PluginCommand(String command) { if (StringUtils.isEmpty(command)) { isValidate = false; return; } String[] syntax = command.trim().split(Constants.SPACE_SPLIT); if (!"plugin".equals(syntax[0])) { isValidate = false; return; } int pluginNameIndex = syntax.length; // fetch all options and allow repetition for (int i = 1; i < syntax.length; ++i) { if (!syntax[i].startsWith("-")) { pluginNameIndex = i; break; } if (syntax[i].startsWith("-") && syntax[i].length() == 1) { isValidate = false; return; } for (int j = 1; j < syntax[i].length(); ++j) { options.add(syntax[i].charAt(j)); } } // only four option can be allowed. for (Character option : options) { switch (option) { case 'h': case 'a': case 'm': case 's': case 'd': continue; default: isValidate = false; return; } } // '-h' or '-a' option can not be combined with other option, such as '-m' if ((options.contains('h') || options.contains('a')) && options.size() > 1) { isValidate = false; return; } // take the rest option as pluginName parameters while (pluginNameIndex < syntax.length) { parameters.add(syntax[pluginNameIndex++]); } // '-h' or '-a' option need not pluginName parameter if ((options.contains('h') || options.contains('a')) && parameters.size() > 0) { isValidate = false; return; } // no parameter is needed when no options if (options.isEmpty()) { isValidate = false; return; } // if option is not 'h' or 'a', parameter should not be empty if (!(options.contains('h') || options.contains('a')) && !options.isEmpty() && parameters.isEmpty()) { isValidate = false; return; } isValidate = true; } boolean isValidate() { return isValidate; } String process() { if (!isValidate) { return "Error command format. Pls type 'plugin -h' to get help message\n"; } StringBuilder sb = new StringBuilder(512); // print plugin command help message if (options.contains('h')) { sb.append(getHelp()); } else if (options.contains('a')) { return pluginList(); } else { Set candidates = pluginManagerService.getAllPluginNames(); boolean matched = false; for (String pattern : parameters) { for (String candidate : candidates) { if (Pattern.matches(pattern, candidate)) { matched = true; sb.append(pluginInfo(candidate)); } } } if (!matched) { sb.append("no matched plugin candidates.").append("\n"); } } return sb.toString(); } String pluginList() { Set pluginNames = pluginManagerService.getAllPluginNames(); StringBuilder sb = new StringBuilder(128); if (pluginNames.isEmpty()) { sb.append("no plugins.").append("\n"); } else { for (String pluginName : pluginNames) { sb.append(pluginName).append("\n"); } } sb.append("plugin count = ").append(pluginNames.size()).append("\n"); return sb.toString(); } String pluginInfo(String pluginName) { Plugin plugin = pluginManagerService.getPluginByName(pluginName); StringBuilder sb = new StringBuilder(256); // print plugin meta info if (options.contains('m')) { sb.append("PluginName: ").append(pluginName).append("\n"); sb.append("Version: ").append(plugin.getVersion()).append("\n"); sb.append("Priority: ").append(plugin.getPriority()).append("\n"); sb.append("Activator: ").append(plugin.getPluginActivator()).append("\n"); sb.append("Export Packages: ") .append(StringUtils.setToStr(plugin.getExportPackages(), ",", "\\")) .append("\n"); sb.append("Import Packages: ") .append(StringUtils.setToStr(plugin.getImportPackages(), ",", "\\")) .append("\n"); sb.append("Export Classes: ") .append(StringUtils.setToStr(plugin.getExportClasses(), ",", "\\")) .append("\n"); sb.append("Import Classes: ") .append(StringUtils.setToStr(plugin.getImportClasses(), ",", "\\")) .append("\n"); sb.append("Export Resources: ") .append(StringUtils.setToStr(plugin.getExportResources(), ",", "\\")) .append("\n"); sb.append("Import Resources: ") .append(StringUtils.setToStr(plugin.getImportResources(), ",", "\\")) .append("\n"); } // print plugin service info if (options.contains('s')) { // TODO } // print plugin detail info if (options.contains('d')) { sb.append("GroupId: ").append(plugin.getGroupId()).append("\n"); sb.append("ArtifactId: ").append(plugin.getArtifactId()).append("\n"); sb.append("Version: ").append(plugin.getVersion()).append("\n"); sb.append("URL: ").append(plugin.getPluginURL()).append("\n"); sb.append("ClassLoader: ").append(plugin.getPluginClassLoader()).append("\n"); sb.append("ClassPath: ").append(join(plugin.getClassPath(), ",")).append("\n"); } sb.append("\n"); return sb.toString(); } String join(URL[] urls, String separator) { Set set = new HashSet<>(); if (urls != null) { for (URL url : urls) { set.add(url.getPath()); } } return StringUtils.setToStr(set, separator, "\\"); } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/plugin/PluginDeployServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.plugin; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.plugin.PluginDeployService; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.Collections; import java.util.List; /** * Service Implementation to deploy ark plugin * * @author ruoshan * @since 0.1.0 */ @Singleton public class PluginDeployServiceImpl implements PluginDeployService { @Inject PluginManagerService pluginManagerService; @Override public void deploy() throws ArkRuntimeException { for (Plugin plugin : pluginManagerService.getPluginsInOrder()) { try { deployPlugin(plugin); } catch (ArkRuntimeException e) { ArkLoggerFactory.getDefaultLogger().error( String.format("Deploy plugin: %s meet error", plugin.getPluginName()), e); throw e; } } } private void deployPlugin(Plugin plugin) throws ArkRuntimeException { try { ArkLoggerFactory.getDefaultLogger().info( String.format("Start to deploy plugin: %s", plugin.getPluginName())); plugin.start(); ArkLoggerFactory.getDefaultLogger().info( String.format("Finish to deploy plugin: %s", plugin.getPluginName())); } catch (ArkRuntimeException e) { ArkLoggerFactory.getDefaultLogger().error( String.format("Start plugin: %s meet error", plugin.getPluginName()), e); throw e; } } @Override public void unDeploy() throws ArkRuntimeException { List pluginsInOrder = pluginManagerService.getPluginsInOrder(); Collections.reverse(pluginsInOrder); for (Plugin plugin : pluginsInOrder) { try { unDeployPlugin(plugin); } catch (ArkRuntimeException e) { ArkLoggerFactory.getDefaultLogger().error( String.format("UnDeploy plugin: %s meet error", plugin.getPluginName()), e); throw e; } } } private void unDeployPlugin(Plugin plugin) throws ArkRuntimeException { try { ArkLoggerFactory.getDefaultLogger().info( String.format("Start to unDeploy plugin: %s", plugin.getPluginName()) + plugin.getPluginName()); plugin.stop(); ArkLoggerFactory.getDefaultLogger().info( String.format("Stop to unDeploy plugin: %s", plugin.getPluginName()) + plugin.getPluginName()); } catch (ArkRuntimeException e) { ArkLoggerFactory.getDefaultLogger().error( String.format("Stop plugin: %s meet error", plugin.getPluginName()), e); throw e; } } @Override public void init() throws ArkRuntimeException { } @Override public void dispose() throws ArkRuntimeException { unDeploy(); } @Override public int getPriority() { return DEFAULT_PRECEDENCE; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/plugin/PluginFactoryServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.plugin; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.model.PluginContextImpl; import com.alipay.sofa.ark.container.model.PluginModel; import com.alipay.sofa.ark.container.service.classloader.PluginClassLoader; import com.alipay.sofa.ark.loader.JarPluginArchive; import com.alipay.sofa.ark.loader.archive.JarFileArchive; import com.alipay.sofa.ark.loader.jar.JarFile; import com.alipay.sofa.ark.spi.archive.Archive; import com.alipay.sofa.ark.spi.archive.PluginArchive; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.model.PluginConfig; import com.alipay.sofa.ark.spi.service.plugin.PluginFactoryService; import com.google.inject.Singleton; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.*; import java.util.jar.Attributes; import static com.alipay.sofa.ark.spi.constant.Constants.*; /** * {@link PluginFactoryService} * * @author qilong.zql * @since 0.4.0 */ @Singleton public class PluginFactoryServiceImpl implements PluginFactoryService { @Override public Plugin createPlugin(PluginArchive pluginArchive) throws IOException, IllegalArgumentException { AssertUtils.isTrue(isArkPlugin(pluginArchive), "Archive must be a ark plugin!"); PluginModel plugin = new PluginModel(); Attributes manifestMainAttributes = pluginArchive.getManifest().getMainAttributes(); plugin .setPluginName(manifestMainAttributes.getValue(PLUGIN_NAME_ATTRIBUTE)) .setGroupId(manifestMainAttributes.getValue(GROUP_ID_ATTRIBUTE)) .setArtifactId(manifestMainAttributes.getValue(ARTIFACT_ID_ATTRIBUTE)) .setVersion(manifestMainAttributes.getValue(PLUGIN_VERSION_ATTRIBUTE)) .setPriority(manifestMainAttributes.getValue(PRIORITY_ATTRIBUTE)) .setPluginActivator(manifestMainAttributes.getValue(ACTIVATOR_ATTRIBUTE)) .setClassPath(pluginArchive.getUrls()) .setPluginUrl(pluginArchive.getUrl()) .setExportClasses(manifestMainAttributes.getValue(EXPORT_CLASSES_ATTRIBUTE)) .setExportPackages(manifestMainAttributes.getValue(EXPORT_PACKAGES_ATTRIBUTE)) .setImportClasses(manifestMainAttributes.getValue(IMPORT_CLASSES_ATTRIBUTE)) .setImportPackages(manifestMainAttributes.getValue(IMPORT_PACKAGES_ATTRIBUTE)) .setImportResources(manifestMainAttributes.getValue(IMPORT_RESOURCES_ATTRIBUTE)) .setExportResources(manifestMainAttributes.getValue(EXPORT_RESOURCES_ATTRIBUTE)) .setPluginClassLoader( new PluginClassLoader(plugin.getPluginName(), plugin.getClassPath())) .setPluginContext(new PluginContextImpl(plugin)); return plugin; } @Override public Plugin createPlugin(PluginArchive pluginArchive, URL[] extensions, Set exportPackages) throws IOException, IllegalArgumentException { AssertUtils.isTrue(isArkPlugin(pluginArchive), "Archive must be a ark plugin!"); if (extensions == null || extensions.length == 0) { return createPlugin(pluginArchive); } PluginModel plugin = new PluginModel(); Attributes manifestMainAttributes = pluginArchive.getManifest().getMainAttributes(); plugin .setPluginName(manifestMainAttributes.getValue(PLUGIN_NAME_ATTRIBUTE)) .setGroupId(manifestMainAttributes.getValue(GROUP_ID_ATTRIBUTE)) .setArtifactId(manifestMainAttributes.getValue(ARTIFACT_ID_ATTRIBUTE)) .setVersion(manifestMainAttributes.getValue(PLUGIN_VERSION_ATTRIBUTE)) .setPriority(manifestMainAttributes.getValue(PRIORITY_ATTRIBUTE)) .setPluginActivator(manifestMainAttributes.getValue(ACTIVATOR_ATTRIBUTE)) .setClassPath(getFinalPluginUrls(pluginArchive, extensions, plugin.getPluginName())) .setPluginUrl(pluginArchive.getUrl()) .setExportMode(manifestMainAttributes.getValue(EXPORT_MODE)) .setExportClasses(manifestMainAttributes.getValue(EXPORT_CLASSES_ATTRIBUTE)) .setExportPackages(manifestMainAttributes.getValue(EXPORT_PACKAGES_ATTRIBUTE), exportPackages) .setImportClasses(manifestMainAttributes.getValue(IMPORT_CLASSES_ATTRIBUTE)) .setImportPackages(manifestMainAttributes.getValue(IMPORT_PACKAGES_ATTRIBUTE)) .setImportResources(manifestMainAttributes.getValue(IMPORT_RESOURCES_ATTRIBUTE)) .setExportResources(manifestMainAttributes.getValue(EXPORT_RESOURCES_ATTRIBUTE)) .setPluginClassLoader( new PluginClassLoader(plugin.getPluginName(), plugin.getClassPath())) .setPluginContext(new PluginContextImpl(plugin)); return plugin; } @Override public Plugin createPlugin(File file) throws IOException { JarFile pluginFile = new JarFile(file); JarFileArchive jarFileArchive = new JarFileArchive(pluginFile); JarPluginArchive jarPluginArchive = new JarPluginArchive(jarFileArchive); return createPlugin(jarPluginArchive); } @Override public Plugin createPlugin(File file, URL[] extensions) throws IOException { JarFile pluginFile = new JarFile(file); JarFileArchive jarFileArchive = new JarFileArchive(pluginFile); JarPluginArchive jarPluginArchive = new JarPluginArchive(jarFileArchive); return createPlugin(jarPluginArchive, extensions, new HashSet<>()); } @Override public Plugin createPlugin(File file, PluginConfig pluginConfig) throws IOException { JarFile pluginFile = new JarFile(file); JarFileArchive jarFileArchive = new JarFileArchive(pluginFile); JarPluginArchive pluginArchive = new JarPluginArchive(jarFileArchive); AssertUtils.isTrue(isArkPlugin(pluginArchive), "Archive must be a ark plugin!"); AssertUtils.isTrue(pluginConfig != null, "PluginConfig must not be null!"); PluginModel plugin = new PluginModel(); Attributes manifestMainAttributes = pluginArchive.getManifest().getMainAttributes(); plugin .setPluginName( !StringUtils.isEmpty(pluginConfig.getSpecifiedName()) ? pluginConfig .getSpecifiedName() : manifestMainAttributes.getValue(PLUGIN_NAME_ATTRIBUTE)) .setGroupId(manifestMainAttributes.getValue(GROUP_ID_ATTRIBUTE)) .setArtifactId(manifestMainAttributes.getValue(ARTIFACT_ID_ATTRIBUTE)) .setVersion( !StringUtils.isEmpty(pluginConfig.getSpecifiedVersion()) ? pluginConfig .getSpecifiedVersion() : manifestMainAttributes .getValue(PLUGIN_VERSION_ATTRIBUTE)) .setPriority(manifestMainAttributes.getValue(PRIORITY_ATTRIBUTE)) .setPluginActivator(manifestMainAttributes.getValue(ACTIVATOR_ATTRIBUTE)) .setClassPath( getFinalPluginUrls(pluginArchive, pluginConfig.getExtensionUrls(), plugin.getPluginName())) .setPluginUrl(pluginArchive.getUrl()) .setExportMode(manifestMainAttributes.getValue(EXPORT_MODE)) .setExportClasses(manifestMainAttributes.getValue(EXPORT_CLASSES_ATTRIBUTE)) .setExportPackages(manifestMainAttributes.getValue(EXPORT_PACKAGES_ATTRIBUTE)) .setImportClasses(manifestMainAttributes.getValue(IMPORT_CLASSES_ATTRIBUTE)) .setImportPackages(manifestMainAttributes.getValue(IMPORT_PACKAGES_ATTRIBUTE)) .setImportResources(manifestMainAttributes.getValue(IMPORT_RESOURCES_ATTRIBUTE)) .setExportResources(manifestMainAttributes.getValue(EXPORT_RESOURCES_ATTRIBUTE)) .setPluginClassLoader( new PluginClassLoader(plugin.getPluginName(), plugin.getClassPath())) .setPluginContext(new PluginContextImpl(plugin)); return plugin; } @Override public Plugin createEmbedPlugin(PluginArchive pluginArchive, ClassLoader masterClassLoader) throws IOException { AssertUtils.isTrue(isArkPlugin(pluginArchive), "Archive must be a ark plugin!"); PluginModel plugin = new PluginModel(); Attributes manifestMainAttributes = pluginArchive.getManifest().getMainAttributes(); boolean enableExportClass = "true".equals(System.getProperty(PLUGIN_EXPORT_CLASS_ENABLE)); boolean enableClassIsolation = "true".equals(System .getProperty(PLUGIN_CLASS_ISOLATION_ENABLE)); boolean overrideExportMode = PluginModel.EXPORTMODE_OVERRIDE.equals(manifestMainAttributes .getValue(EXPORT_MODE)); plugin .setPluginName(manifestMainAttributes.getValue(PLUGIN_NAME_ATTRIBUTE)) .setGroupId(manifestMainAttributes.getValue(GROUP_ID_ATTRIBUTE)) .setArtifactId(manifestMainAttributes.getValue(ARTIFACT_ID_ATTRIBUTE)) .setVersion(manifestMainAttributes.getValue(PLUGIN_VERSION_ATTRIBUTE)) .setPriority(manifestMainAttributes.getValue(PRIORITY_ATTRIBUTE)) .setPluginActivator(manifestMainAttributes.getValue(ACTIVATOR_ATTRIBUTE)) .setClassPath( (enableClassIsolation || overrideExportMode) ? pluginArchive.getUrls() : ClassLoaderUtils.getURLs(masterClassLoader)) .setPluginUrl(pluginArchive.getUrl()) .setExportMode(manifestMainAttributes.getValue(EXPORT_MODE)) .setExportClasses( enableExportClass ? manifestMainAttributes.getValue(EXPORT_CLASSES_ATTRIBUTE) : null) .setExportPackages( enableExportClass ? manifestMainAttributes.getValue(EXPORT_PACKAGES_ATTRIBUTE) : null) .setImportClasses(manifestMainAttributes.getValue(IMPORT_CLASSES_ATTRIBUTE)) .setImportPackages(manifestMainAttributes.getValue(IMPORT_PACKAGES_ATTRIBUTE)) .setImportResources(manifestMainAttributes.getValue(IMPORT_RESOURCES_ATTRIBUTE)) .setExportResources(manifestMainAttributes.getValue(EXPORT_RESOURCES_ATTRIBUTE)) .setPluginClassLoader( (enableClassIsolation || overrideExportMode) ? new PluginClassLoader(plugin .getPluginName(), plugin.getClassPath()) : masterClassLoader) .setPluginContext(new PluginContextImpl(plugin)); return plugin; } private URL[] getFinalPluginUrls(PluginArchive pluginArchive, URL[] extensions, String pluginName) throws IOException { URL[] urls = pluginArchive.getUrls(); List urlList = new ArrayList<>(Arrays.asList(urls)); urlList.remove(null); // get config by PLUGIN-EXPORT key, exclude jar by config String excludeArtifact = ArkConfigs.getStringValue(String.format(PLUGIN_EXTENSION_FORMAT, pluginName)); if (!StringUtils.isEmpty(excludeArtifact)) { List preRemoveList = new ArrayList<>(); for (URL url : urlList) { String[] dependencies = excludeArtifact.split(STRING_SEMICOLON); for (String dependency : dependencies) { String artifactId = dependency.split(STRING_COLON)[0]; String version = dependency.split(STRING_COLON)[1]; if (url.getPath().endsWith(artifactId + "-" + version + ".jar!/")) { preRemoveList.add(url); break; } } } urlList.removeAll(preRemoveList); } // add extension urls to plugin classloader classpath if (extensions != null && extensions.length > 0) { pluginArchive.setExtensionUrls(extensions); urlList.addAll(Arrays.asList(extensions)); } return urlList.toArray(new URL[0]); } private boolean isArkPlugin(PluginArchive pluginArchive) { return pluginArchive.isEntryExist(new Archive.EntryFilter() { @Override public boolean matches(Archive.Entry entry) { return !entry.isDirectory() && entry.getName().equals(Constants.ARK_PLUGIN_MARK_ENTRY); } }); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/plugin/PluginManagerServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.plugin; import com.alipay.sofa.ark.common.util.OrderComparator; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import com.alipay.sofa.ark.spi.model.Plugin; import com.google.inject.Singleton; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * Service Implementation to manager ark plugin * * @author ruoshan * @since 0.1.0 */ @Singleton public class PluginManagerServiceImpl implements PluginManagerService { private ConcurrentHashMap plugins = new ConcurrentHashMap<>(); @Override public void registerPlugin(Plugin plugin) { if (plugins.putIfAbsent(plugin.getPluginName(), plugin) != null) { throw new ArkRuntimeException(String.format("duplicate plugin: %s exists.", plugin.getPluginName())); } } @Override public Plugin getPluginByName(String pluginName) { return plugins.get(pluginName); } @Override public Set getAllPluginNames() { return plugins.keySet(); } @Override public List getPluginsInOrder() { List pluginList = new ArrayList<>(plugins.values()); Collections.sort(pluginList, new OrderComparator()); return pluginList; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/registry/RegistryServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.registry; import com.alipay.sofa.ark.common.log.ArkLogger; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.OrderComparator; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.registry.DefaultServiceFilter; import com.alipay.sofa.ark.container.registry.ServiceMetadataImpl; import com.alipay.sofa.ark.container.registry.ServiceReferenceImpl; import com.alipay.sofa.ark.spi.registry.*; import com.alipay.sofa.ark.spi.service.injection.InjectionService; import com.alipay.sofa.ark.spi.service.registry.RegistryService; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArraySet; /** * Registry Service Implement * * @author ruoshan * @since 0.1.0 */ @Singleton public class RegistryServiceImpl implements RegistryService { private CopyOnWriteArraySet> services = new CopyOnWriteArraySet<>(); private OrderComparator orderComparator = new OrderComparator(); @Inject private InjectionService injectionService; @Override public ServiceReference publishService(Class ifClass, T implObject, ServiceProvider serviceProvider) { return publishService(ifClass, implObject, StringUtils.EMPTY_STRING, serviceProvider); } @Override @SuppressWarnings("unchecked") public ServiceReference publishService(Class ifClass, T implObject, String uniqueId, ServiceProvider serviceProvider) { AssertUtils.assertNotNull(ifClass, "Service interface should not be null."); AssertUtils.assertNotNull(implObject, "Service implementation should not be null."); AssertUtils.assertNotNull(uniqueId, "Service uniqueId should not be null."); AssertUtils.assertNotNull(serviceProvider, "ServiceProvider should not be null."); ServiceMetadata serviceMetadata = new ServiceMetadataImpl(ifClass, uniqueId, serviceProvider); for (ServiceReference serviceReference : services) { if (serviceMetadata.equals(serviceReference.getServiceMetadata())) { ArkLoggerFactory.getDefaultLogger().warn( String.format("Service: %s publish by: %s already exist", serviceMetadata.getServiceName(), serviceProvider)); return (ServiceReference) serviceReference; } } ServiceReference serviceReference = new ServiceReferenceImpl<>(serviceMetadata, implObject); injectionService.inject(serviceReference); ArkLoggerFactory.getDefaultLogger().info( String.format("Service: %s publish by: %s succeed", serviceMetadata.getServiceName(), serviceProvider)); services.add(serviceReference); return serviceReference; } @SuppressWarnings("unchecked") @Override public ServiceReference referenceService(Class ifClass) { return referenceService(ifClass, StringUtils.EMPTY_STRING); } @Override @SuppressWarnings("unchecked") public ServiceReference referenceService(Class ifClass, String uniqueId) { List> references = referenceServices(ifClass, uniqueId); return references.isEmpty() ? null : references.get(0); } @Override @SuppressWarnings("unchecked") public List> referenceServices(Class ifClass) { return referenceServices(ifClass, StringUtils.EMPTY_STRING); } @Override @SuppressWarnings("unchecked") public List> referenceServices(Class ifClass, String uniqueId) { DefaultServiceFilter defaultServiceFilter = new DefaultServiceFilter<>(); // only conditional on interface and uniqueId defaultServiceFilter.setServiceInterface(ifClass).setUniqueId(uniqueId); List> references = referenceServices(defaultServiceFilter); return references; } @Override @SuppressWarnings("unchecked") public List> referenceServices(ServiceFilter serviceFilter) { List> serviceReferences = new ArrayList<>(); for (ServiceReference reference : services) { if (serviceFilter.match(reference)) { serviceReferences.add((ServiceReference) reference); } } Collections.sort(serviceReferences, orderComparator); return serviceReferences; } @Override public int unPublishServices(ServiceFilter serviceFilter) { int count = 0; for (ServiceReference reference : services) { if (serviceFilter.match(reference)) { services.remove(reference); count += 1; } } return count; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/retrieval/ClassInfoMethod.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.retrieval; import java.lang.reflect.Modifier; import java.security.CodeSource; import java.util.ArrayList; import java.util.List; /** * Handle class information display * * @author yanzhu * @since 2.2.4 */ public class ClassInfoMethod { /** * empty string */ private static final String EMPTY_STRING = ""; /** * get code source of class */ private static String getCodeSource(final CodeSource cs) { if (null == cs || null == cs.getLocation() || null == cs.getLocation().getFile()) { return EMPTY_STRING; } return cs.getLocation().getFile(); } /** * get the complete class name * * @param clazz * @return */ private static String getClassName(Class clazz) { if (clazz.isArray()) { StringBuilder sb = new StringBuilder(clazz.getName()); sb.delete(0, 2); if (sb.length() > 0 && sb.charAt(sb.length() - 1) == ';') { sb.deleteCharAt(sb.length() - 1); } sb.append("[]"); return sb.toString(); } else { return clazz.getName(); } } /** * get modifier of class * * @param mod modifier * @return 翻译值 */ private static String getModifier(int mod, char splitter) { StringBuilder sb = new StringBuilder(); if (Modifier.isAbstract(mod)) { sb.append("abstract").append(splitter); } if (Modifier.isFinal(mod)) { sb.append("final").append(splitter); } if (Modifier.isInterface(mod)) { sb.append("interface").append(splitter); } if (Modifier.isNative(mod)) { sb.append("native").append(splitter); } if (Modifier.isPrivate(mod)) { sb.append("private").append(splitter); } if (Modifier.isProtected(mod)) { sb.append("protected").append(splitter); } if (Modifier.isPublic(mod)) { sb.append("public").append(splitter); } if (Modifier.isStatic(mod)) { sb.append("static").append(splitter); } if (Modifier.isStrict(mod)) { sb.append("strict").append(splitter); } if (Modifier.isSynchronized(mod)) { sb.append("synchronized").append(splitter); } if (Modifier.isTransient(mod)) { sb.append("transient").append(splitter); } if (Modifier.isVolatile(mod)) { sb.append("volatile").append(splitter); } if (sb.length() > 0) { sb.deleteCharAt(sb.length() - 1); } return sb.toString(); } /** * get all super classes of current class * * @param clazz * @return */ private static String[] getSuperClass(Class clazz) { List list = new ArrayList(); Class superClass = clazz.getSuperclass(); if (null != superClass) { list.add(ClassInfoMethod.getClassName(superClass)); while (true) { superClass = superClass.getSuperclass(); if (null == superClass) { break; } list.add(ClassInfoMethod.getClassName(superClass)); } } return list.toArray(new String[0]); } /** * get all classloaders of class * * @param clazz * @return */ private static String[] getClassloader(Class clazz) { List list = new ArrayList(); ClassLoader loader = clazz.getClassLoader(); if (null != loader) { list.add(loader.toString()); while (true) { loader = loader.getParent(); if (null == loader) { break; } list.add(loader.toString()); } } return list.toArray(new String[0]); } /** * create complete class information * * @param clazz * @param bizName * @return */ public static String createClassInfo(Class clazz, String bizName) { ClassInfoVO classInfo = new ClassInfoVO(); classInfo.setClassInfo(ClassInfoMethod.getClassName(clazz)); classInfo.setCodeSource(ClassInfoMethod.getCodeSource(clazz.getProtectionDomain() .getCodeSource())); classInfo.setInterface(clazz.isInterface()); classInfo.setAnnotation(clazz.isAnnotation()); classInfo.setEnum(clazz.isEnum()); classInfo.setContainerName(bizName); classInfo.setSimpleName(clazz.getSimpleName()); classInfo.setModifier(ClassInfoMethod.getModifier(clazz.getModifiers(), ',')); classInfo.setSuperClass(ClassInfoMethod.getSuperClass(clazz)); classInfo.setClassloader(ClassInfoMethod.getClassloader(clazz)); return ViewRender.renderClassInfo(classInfo); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/retrieval/ClassInfoVO.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.retrieval; import java.util.Arrays; /** * Class Detail Info VO Of Telnet 'ck' Command * * @author yanzhu * @since 2.2.4 */ public class ClassInfoVO { private String classInfo; private String codeSource; private Boolean isInterface; private Boolean isAnnotation; private Boolean isEnum; private String containerName; private String simpleName; private String modifier; private String[] superClass; private String[] classloader; public ClassInfoVO() { } public String getClassInfo() { return classInfo; } public void setClassInfo(String classInfo) { this.classInfo = classInfo; } public String getCodeSource() { return codeSource; } public void setCodeSource(String codeSource) { this.codeSource = codeSource; } public boolean isInterface() { return isInterface; } public void setInterface(boolean anInterface) { isInterface = anInterface; } public boolean isAnnotation() { return isAnnotation; } public void setAnnotation(boolean annotation) { isAnnotation = annotation; } public boolean isEnum() { return isEnum; } public void setEnum(boolean anEnum) { isEnum = anEnum; } public String getContainerName() { return containerName; } public void setContainerName(String containerName) { this.containerName = containerName; } public String getSimpleName() { return simpleName; } public void setSimpleName(String simpleName) { this.simpleName = simpleName; } public String getModifier() { return modifier; } public void setModifier(String modifier) { this.modifier = modifier; } public String[] getSuperClass() { return superClass; } public void setSuperClass(String[] superClass) { this.superClass = superClass; } public String[] getClassloader() { return classloader; } public void setClassloader(String[] classloader) { this.classloader = classloader; } @Override public String toString() { return "ClassDetailVO{" + "classInfo='" + classInfo + '\'' + ", codeSource='" + codeSource + '\'' + ", isInterface=" + isInterface + ", isAnnotation=" + isAnnotation + ", isEnum=" + isEnum + ", containerName='" + containerName + '\'' + ", simpleName='" + simpleName + '\'' + ", modifier='" + modifier + '\'' + ", superClass=" + Arrays.toString(superClass) + ", classloader=" + Arrays.toString(classloader) + '}'; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/retrieval/InfoQueryCommandProvider.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.retrieval; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.common.log.ArkLogger; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.EnvironmentUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.service.classloader.BizClassLoader; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.service.session.CommandProvider; import net.bytebuddy.agent.ByteBuddyAgent; import java.lang.instrument.Instrumentation; import java.util.HashSet; import java.util.Set; /** * @author yanzhu * @since 2.2.4 */ public class InfoQueryCommandProvider implements CommandProvider { @Override public String getHelp() { return HELP_MESSAGE; } @Override public String handleCommand(String command) { return new ContainerQueryInfoCommand(command).process(); } @Override public boolean validate(String command) { return new ContainerQueryInfoCommand(command).isValidate(); } private static final String HELP_MESSAGE = "Query Class Info Or Other Info Command Tips:\n" + " USAGE: plugin [option...] [className/beanName...]\n" + " SAMPLE: ck -c com.sofa.ark.HelloWorld\n" + " -h Shows the help message.\n" + " -c Shows the class info.\n"; class ContainerQueryInfoCommand { private boolean isValidate; private Set options = new HashSet<>(); private Set parameters = new HashSet<>(); private final Instrumentation instrumentation = ByteBuddyAgent.install(); ContainerQueryInfoCommand(String command) { if (StringUtils.isEmpty(command)) { isValidate = false; return; } String[] syntax = command.trim().split(Constants.SPACE_SPLIT); if (!"ck".equals(syntax[0])) { isValidate = false; return; } int argumentIndex = syntax.length; // fetch all options and allow repetition for (int i = 1; i < syntax.length; ++i) { if (!syntax[i].startsWith("-")) { argumentIndex = i; break; } if (syntax[i].startsWith("-") && syntax[i].length() == 1) { isValidate = false; return; } for (int j = 1; j < syntax[i].length(); ++j) { options.add(syntax[i].charAt(j)); } } // only the following option can be allowed. for (Character option : options) { switch (option) { case 'h': case 'c': continue; default: isValidate = false; return; } } // check whether options is empty if (options.isEmpty()) { isValidate = false; return; } // '-h' or '-c' option can not be combined with other option if (options.contains('h') || options.contains('c')) { if (options.size() > 1) { isValidate = false; return; } } // take the rest option as parameters while (argumentIndex < syntax.length) { parameters.add(syntax[argumentIndex++]); } // '-h' option need not any parameter if (options.contains('h') && parameters.size() > 0) { isValidate = false; return; } // if option is not 'h' or 'a', parameter should not be empty if (!options.contains('h') && parameters.isEmpty()) { isValidate = false; return; } // if option is 'c', parameter count should be only one. if (options.contains('c')) { if (parameters.size() > 1) { isValidate = false; return; } } isValidate = true; } boolean isValidate() { return isValidate; } String process() { if (!isValidate) { return "Error command format. Pls type 'ck -h' to get help message\n"; } if (options.contains('h')) { return HELP_MESSAGE; } else if (options.contains('c')) { return queryClass(); } else { return HELP_MESSAGE; } } String queryClass() { if (EnvironmentUtils.isOpenSecurity()) { return "Cannot execute 'ck -c' command in security mode.\n"; } String param = parameters.toArray(new String[] {})[0]; Set> matches = new HashSet>(); for (Class c : instrumentation.getAllLoadedClasses()) { if (c == null) { continue; } if (param.equals(c.getName())) { matches.add(c); } } if (matches.isEmpty()) { return "Can not find class : " + param; } return createClassInfo(matches).toString(); } StringBuilder createClassInfo(Set> classSet) { StringBuilder sb = new StringBuilder(); for (Class clazz : classSet) { ClassLoader classLoader = clazz.getClassLoader(); if (null != ArkClient.getMasterBiz() && classLoader == ArkClient.getMasterBiz().getBizClassLoader()) { String classInfo = ClassInfoMethod.createClassInfo(clazz, ArkClient .getMasterBiz().getIdentity()); sb.append(classInfo).append("\n"); } else if (null != ArkClient.getMasterBiz() && classLoader instanceof BizClassLoader) { String classInfo = ClassInfoMethod.createClassInfo(clazz, ((BizClassLoader) classLoader).getBizIdentity()); sb.append(classInfo).append("\n"); } else { String classInfo = ClassInfoMethod.createClassInfo(clazz, classLoader.toString()); sb.append(classInfo).append("\n"); } } return sb; } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/retrieval/ViewRender.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.retrieval; import com.taobao.text.Decoration; import com.taobao.text.ui.Element; import com.taobao.text.ui.TableElement; import com.taobao.text.ui.TreeElement; import com.taobao.text.util.RenderUtil; /** * Render Telnet Panel * * @author yanzhu * @since 2.2.4 */ public class ViewRender { /** * render class information * * @param clazz * @return */ public static String renderClassInfo(ClassInfoVO clazz) { TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1); table .row(Element.label("class-info").style(Decoration.bold.bold()), Element.label(clazz.getClassInfo())) .row(Element.label("code-source").style(Decoration.bold.bold()), Element.label(clazz.getCodeSource())) .row(Element.label("isInterface").style(Decoration.bold.bold()), Element.label("" + clazz.isInterface())) .row(Element.label("isAnnotation").style(Decoration.bold.bold()), Element.label("" + clazz.isAnnotation())) .row(Element.label("isEnum").style(Decoration.bold.bold()), Element.label("" + clazz.isEnum())) .row(Element.label("container-name").style(Decoration.bold.bold()), Element.label("" + clazz.getContainerName())) .row(Element.label("simple-name").style(Decoration.bold.bold()), Element.label(clazz.getSimpleName())) .row(Element.label("modifier").style(Decoration.bold.bold()), Element.label(clazz.getModifier())) .row(Element.label("super-class").style(Decoration.bold.bold()), drawSuperClass(clazz)) .row(Element.label("class-loader").style(Decoration.bold.bold()), drawClassLoader(clazz.getClassloader())); return RenderUtil.render(table); } private static Element drawSuperClass(ClassInfoVO clazz) { return drawTree(clazz.getSuperClass()); } private static Element drawClassLoader(String[] classloaders) { return drawTree(classloaders); } private static Element drawTree(String[] nodes) { TreeElement root = new TreeElement(); TreeElement parent = root; for (String node : nodes) { TreeElement child = new TreeElement(Element.label(node)); parent.addChild(child); parent = child; } return root; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/session/NettyTelnetServer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.session; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.EnvironmentUtils; import com.alipay.sofa.ark.container.session.handler.ArkCommandHandler; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import java.util.concurrent.Executor; import static com.alipay.sofa.ark.spi.constant.Constants.CHANNEL_QUIT; import static com.alipay.sofa.ark.spi.constant.Constants.LOCAL_HOST; /** * @author qilong.zql * @since 1.0.0 */ public class NettyTelnetServer { private int port; private ServerBootstrap serverBootstrap; private EventLoopGroup bossGroup; private EventLoopGroup workerGroup; private Channel channel; public NettyTelnetServer(int port, Executor executor) { this.port = port; bossGroup = new NioEventLoopGroup(1, executor); workerGroup = new NioEventLoopGroup(1, executor); } public void open() throws InterruptedException { serverBootstrap = new ServerBootstrap(); serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024); serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)).childHandler(new NettyTelnetInitializer()); channel = serverBootstrap.bind(port).sync().channel(); } public void close() { channel.close(); bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } static class NettyTelnetInitializer extends ChannelInitializer { private static StringDecoder DECODER = new StringDecoder(); private static StringEncoder ENCODER = new StringEncoder(); @Override protected void initChannel(SocketChannel channel) throws Exception { if (EnvironmentUtils.isOpenSecurity()) { if (!channel.remoteAddress().getHostName().equals(LOCAL_HOST)) { return; } } ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); pipeline.addLast(ENCODER); pipeline.addLast(DECODER); pipeline.addLast(new NettyTelnetHandler()); } } static class NettyTelnetHandler extends SimpleChannelInboundHandler { private static ArkCommandHandler arkCommandHandler = new ArkCommandHandler(); @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); ctx.write(arkCommandHandler.promptMessage()); ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ArkLoggerFactory.getDefaultLogger() .error("Error occurs in netty telnet server.", cause); super.exceptionCaught(ctx, cause); } @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { if (CHANNEL_QUIT.contains(msg)) { ctx.channel().close(); return; } ctx.write(arkCommandHandler.responseMessage(msg)); ctx.flush(); } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/session/StandardTelnetServerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.session; import com.alipay.sofa.ark.common.log.ArkLogger; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.thread.CommonThreadPool; import com.alipay.sofa.ark.common.thread.ThreadPoolManager; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.EnvironmentUtils; import com.alipay.sofa.ark.common.util.PortSelectUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.service.session.TelnetServerService; import com.google.inject.Singleton; import java.util.concurrent.atomic.AtomicBoolean; import static com.alipay.sofa.ark.spi.constant.Constants.DEFAULT_SELECT_PORT_SIZE; import static com.alipay.sofa.ark.spi.constant.Constants.DEFAULT_TELNET_PORT; import static com.alipay.sofa.ark.spi.constant.Constants.TELNET_PORT_ATTRIBUTE; import static com.alipay.sofa.ark.spi.constant.Constants.TELNET_SERVER_ENABLE; /** * {@link TelnetServerService} * * @author qilong.zql * @since 0.4.0 */ @Singleton public class StandardTelnetServerImpl implements TelnetServerService { private static final int WORKER_THREAD_POOL_SIZE = 2; private int port = -1; private AtomicBoolean shutdown = new AtomicBoolean(false); private boolean enableTelnetServer = EnvironmentUtils.getProperty( TELNET_SERVER_ENABLE, "true") .equalsIgnoreCase("true"); private NettyTelnetServer nettyTelnetServer; public StandardTelnetServerImpl() { if (enableTelnetServer) { String telnetPort = EnvironmentUtils.getProperty(TELNET_PORT_ATTRIBUTE); try { if (!StringUtils.isEmpty(telnetPort)) { port = Integer.parseInt(telnetPort); } else { port = PortSelectUtils.selectAvailablePort(DEFAULT_TELNET_PORT, DEFAULT_SELECT_PORT_SIZE); } } catch (NumberFormatException e) { ArkLoggerFactory.getDefaultLogger().error( String.format("Invalid port in %s", telnetPort), e); throw new ArkRuntimeException(e); } } } @Override public void run() { AssertUtils.isTrue(port > 0, "Telnet port should be positive integer."); try { ArkLoggerFactory.getDefaultLogger().info("Listening on port: " + port); CommonThreadPool workerPool = new CommonThreadPool() .setCorePoolSize(WORKER_THREAD_POOL_SIZE).setDaemon(true) .setThreadPoolName(Constants.TELNET_SERVER_WORKER_THREAD_POOL_NAME); ThreadPoolManager.registerThreadPool(Constants.TELNET_SERVER_WORKER_THREAD_POOL_NAME, workerPool); nettyTelnetServer = new NettyTelnetServer(port, workerPool.getExecutor()); nettyTelnetServer.open(); } catch (InterruptedException e) { ArkLoggerFactory.getDefaultLogger().error("Unable to open netty telnet server.", e); throw new ArkRuntimeException(e); } } @Override public void shutdown() { if (shutdown.compareAndSet(false, true)) { try { if (nettyTelnetServer != null) { nettyTelnetServer.close(); nettyTelnetServer = null; } } catch (Throwable t) { ArkLoggerFactory.getDefaultLogger().error( "An error occurs when shutdown telnet server.", t); throw new ArkRuntimeException(t); } } } @Override public void init() throws ArkRuntimeException { if (enableTelnetServer) { run(); } else { ArkLoggerFactory.getDefaultLogger().warn("Telnet server is disabled."); } } @Override public void dispose() throws ArkRuntimeException { if (enableTelnetServer) { shutdown(); } } @Override public int getPriority() { return HIGHEST_PRECEDENCE; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/session/handler/AbstractTerminalTypeMapping.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.session.handler; import java.io.File; import java.util.HashMap; import java.util.Map; /** * Map special key value according to different terminal type * * @author qilong.zql * @since 0.4.0 */ public abstract class AbstractTerminalTypeMapping { public static String getDefaultTerminalType() { return File.separatorChar == '/' ? "XTERM" : "ANSI"; } protected Map escKeys; protected byte backSpace; protected byte del; public AbstractTerminalTypeMapping(byte backSpace, byte del) { this.backSpace = backSpace; this.del = del; escKeys = new HashMap<>(); escKeys.put("[C", KEYS.RIGHT); escKeys.put("[D", KEYS.LEFT); escKeys.put("[3~", KEYS.DEL); } public byte getBackspace() { return backSpace; } public byte getDel() { return del; } public KEYS getMatchKeys(String str) { if (escKeys.get(str) != null) { return escKeys.get(str); } if (isPossibleEscKeys(str)) { return KEYS.UNFINISHED; } return KEYS.UNKNOWN; } protected boolean isPossibleEscKeys(String str) { for (String key : escKeys.keySet()) { if (key.startsWith(str)) { return true; } } return false; } public enum KEYS { RIGHT, LEFT, DEL, UNFINISHED, UNKNOWN } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/session/handler/ArkCommandHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.session.handler; import com.alipay.sofa.ark.common.thread.CommonThreadPool; import com.alipay.sofa.ark.common.thread.ThreadPoolManager; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.registry.ServiceReference; import com.alipay.sofa.ark.spi.service.registry.RegistryService; import com.alipay.sofa.ark.spi.service.session.CommandProvider; import java.util.List; /** * Collect implementation of {@link com.alipay.sofa.ark.spi.service.session.CommandProvider} * * @author qilong.zql * @since 0.4.0 */ public class ArkCommandHandler { private RegistryService registryService; static { init(); } private static void init() { CommonThreadPool commandPool = new CommonThreadPool().setAllowCoreThreadTimeOut(true) .setThreadPoolName(Constants.TELNET_COMMAND_THREAD_POOL_NAME).setDaemon(true); ThreadPoolManager .registerThreadPool(Constants.TELNET_COMMAND_THREAD_POOL_NAME, commandPool); } public ArkCommandHandler() { registryService = ArkServiceContainerHolder.getContainer() .getService(RegistryService.class); } public String handleCommand(String cmdLine) { if (StringUtils.isEmpty(cmdLine)) { return StringUtils.EMPTY_STRING; } List> commandProviders = registryService .referenceServices(CommandProvider.class, null); for (ServiceReference commandService : commandProviders) { CommandProvider commandProvider = commandService.getService(); if (commandProvider.validate(cmdLine)) { return commandProvider.handleCommand(cmdLine); } } return helpMessage(commandProviders); } public String helpMessage(List> commandProviders) { StringBuilder sb = new StringBuilder(); for (ServiceReference commandService : commandProviders) { CommandProvider commandProvider = commandService.getService(); sb.append(commandProvider.getHelp()); } return sb.toString(); } public String promptMessage() { return Constants.TELNET_SESSION_PROMPT; } public String responseMessage(String cmd) { String commandResult = handleCommand(cmd); commandResult = commandResult.replace("\n", Constants.TELNET_STRING_END); if (StringUtils.isEmpty(commandResult)) { commandResult = Constants.TELNET_STRING_END; } else if (!commandResult.endsWith(Constants.TELNET_STRING_END)) { commandResult = commandResult + Constants.TELNET_STRING_END + Constants.TELNET_STRING_END; } else if (!commandResult.endsWith(Constants.TELNET_STRING_END .concat(Constants.TELNET_STRING_END))) { commandResult = commandResult + Constants.TELNET_STRING_END; } commandResult = commandResult + promptMessage(); return commandResult; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/test/NoneDelegateTestClassLoader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.test; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.container.service.classloader.BizClassLoader; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import java.net.URL; /** * @author caojie.cj * @since 0.1.0 */ public class NoneDelegateTestClassLoader extends BizClassLoader { public NoneDelegateTestClassLoader(String bizIdentity, URL[] urls) { super(bizIdentity, urls); // since version 1.1.0, we support load extension from ark biz, we should register biz now. BizManagerService bizManagerService = ArkServiceContainerHolder.getContainer().getService( BizManagerService.class); Biz testBiz = createTestBiz(bizIdentity); bizManagerService.registerBiz(testBiz); ((BizModel) testBiz).setBizState(BizState.ACTIVATED); } private Biz createTestBiz(String bizIdentity) { String[] bizNameAndVersion = bizIdentity.split(":"); if (bizNameAndVersion.length != 2) { throw new ArkRuntimeException("error bizIdentity format."); } BizManagerService bizManagerService = ArkServiceContainerHolder.getContainer().getService( BizManagerService.class); Biz testBiz = new BizModel().setBizName(bizNameAndVersion[0]) .setBizVersion(bizNameAndVersion[1]).setClassLoader(this).setDenyImportPackages("") .setDenyImportClasses("").setDenyImportResources("").setBizState(BizState.RESOLVED); bizManagerService.registerBiz(testBiz); return testBiz; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/test/TestClassLoader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.test; import com.alipay.sofa.ark.common.util.ClassUtils; import com.alipay.sofa.ark.common.util.EnvironmentUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.container.service.classloader.BizClassLoader; import com.alipay.sofa.ark.exception.ArkLoaderException; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import java.net.URL; import java.util.Arrays; import java.util.List; /** * @author qilong.zql * @since 0.1.0 */ public class TestClassLoader extends BizClassLoader { private final ClassLoader delegateClassLoader; private static String[] packageForTest = { // Junit "org.junit", "junit", "org.hamcrest", // TestNG "org.testng", "com.beust.jcommander", "bsh", // mockito "org.mockito", // Ark "com.alipay.sofa.ark.support.common", // tomcat "org.apache.catalina", "org.apache.coyote", "org.apache.juli", "org.apache.naming", "org.apache.tomcat", "org.apache.el", "javax" }; private List delegateClassToAppClassLoader; private List delegateClassToTestClassLoader; public TestClassLoader(String bizIdentity, URL[] urls, ClassLoader delegate) { super(bizIdentity, urls, true); delegateClassLoader = delegate; // since version 1.1.0, we support load extension from ark biz, we should register biz now. BizManagerService bizManagerService = ArkServiceContainerHolder.getContainer().getService( BizManagerService.class); Biz testBiz = createTestBiz(bizIdentity); bizManagerService.registerBiz(testBiz); ((BizModel) testBiz).setBizState(BizState.ACTIVATED); super.setBizModel((BizModel) testBiz); } @Override protected Class loadClassInternal(String name, boolean resolve) throws ArkLoaderException { if (isDelegateToAppClassLoader(ClassUtils.getPackageName(name))) { try { return delegateClassLoader.loadClass(name); } catch (ClassNotFoundException e) { throw new ArkLoaderException(String.format( "[TestClass Loader] %s : can not load class: %s", getBizIdentity(), name)); } } else { return super.loadClassInternal(name, resolve); } } private boolean isDelegateToAppClassLoader(String name) { if (delegateClassToAppClassLoader == null) { String classes = EnvironmentUtils.getProperty( Constants.FORCE_DELEGATE_TO_APP_CLASSLOADER, Constants.EMPTY_STR); delegateClassToAppClassLoader = Arrays.asList(classes.split(Constants.COMMA_SPLIT)); } if (delegateClassToTestClassLoader == null) { String classes = EnvironmentUtils.getProperty( Constants.FORCE_DELEGATE_TO_TEST_CLASSLOADER, Constants.EMPTY_STR); delegateClassToTestClassLoader = Arrays.asList(classes.split(Constants.COMMA_SPLIT)); } for (String pkg : delegateClassToAppClassLoader) { if (!StringUtils.isEmpty(pkg) && name.startsWith(pkg)) { return true; } } for (String pkg : delegateClassToTestClassLoader) { if (!StringUtils.isEmpty(pkg) && name.startsWith(pkg)) { return false; } } for (String pkg : packageForTest) { if (name.startsWith(pkg)) { return true; } } return false; } private Biz createTestBiz(String bizIdentity) { String[] bizNameAndVersion = bizIdentity.split(":"); if (bizNameAndVersion.length != 2) { throw new ArkRuntimeException("error bizIdentity format."); } BizManagerService bizManagerService = ArkServiceContainerHolder.getContainer().getService( BizManagerService.class); Biz testBiz = new BizModel().setBizName(bizNameAndVersion[0]) .setBizVersion(bizNameAndVersion[1]).setClassLoader(this).setDenyImportPackages("") .setDenyImportClasses("").setDenyImportResources("").setBizState(BizState.RESOLVED); bizManagerService.registerBiz(testBiz); return testBiz; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/test/TestHelper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.test; import com.alipay.sofa.ark.container.ArkContainer; import com.alipay.sofa.ark.spi.pipeline.PipelineContext; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderService; import java.net.URL; /** * @author qilong.zql * @since 0.1.0 */ public class TestHelper { private final static String MOCK_BIZ_IDENTITY = "Mock Biz:Mock Version"; private ArkContainer arkContainer; public TestHelper(Object object) { this.arkContainer = (ArkContainer) object; } public ClassLoader createTestClassLoader() { PipelineContext context = arkContainer.getPipelineContext(); URL[] classpath = context.getLaunchCommand().getClasspath(); ClassLoaderService classloaderService = arkContainer.getArkServiceContainer().getService( ClassLoaderService.class); return new TestClassLoader(MOCK_BIZ_IDENTITY, classpath, classloaderService.getSystemClassLoader()); } public ClassLoader createNoneDelegateTestClassLoader() { PipelineContext context = arkContainer.getPipelineContext(); URL[] classpath = context.getLaunchCommand().getClasspath(); ClassLoaderService classloaderService = arkContainer.getArkServiceContainer().getService( ClassLoaderService.class); return new NoneDelegateTestClassLoader(MOCK_BIZ_IDENTITY, classpath); } public boolean isStarted() { return arkContainer.isStarted(); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/main/resources/META-INF/services/com.alipay.sofa.ark.common.guice.AbstractArkGuiceModule ================================================ com.alipay.sofa.ark.container.guice.ContainerModule ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/ArkContainerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.session.handler.ArkCommandHandler; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.loader.ExecutableArkBizJar; import com.alipay.sofa.ark.loader.archive.JarFileArchive; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.service.biz.BizFactoryService; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.extension.ArkServiceLoader; import org.junit.Assert; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import java.net.URL; import static com.alipay.sofa.ark.container.ArkContainer.main; import static com.alipay.sofa.ark.spi.constant.Constants.TELNET_SESSION_PROMPT; import static com.alipay.sofa.ark.spi.constant.Constants.TELNET_STRING_END; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** * @author ruoshan * @since 0.1.0 */ public class ArkContainerTest extends BaseTest { private URL jarURL = ArkContainerTest.class.getClassLoader().getResource("test.jar"); @Override public void before() { // no op } @Override public void after() { // no op } @Test public void testStart() throws ArkRuntimeException { String[] args = new String[] { "-Ajar=" + jarURL.toExternalForm() }; ArkContainer arkContainer = (ArkContainer) main(args); assertTrue(arkContainer.isStarted()); arkContainer.stop(); } @Test public void testStop() throws Exception { ArkContainer arkContainer = new ArkContainer(new ExecutableArkBizJar(new JarFileArchive( FileUtils.file((jarURL.getFile()))))); arkContainer.start(); arkContainer.stop(); Assert.assertFalse(arkContainer.isRunning()); } @Test public void testArkServiceLoader() throws ArkRuntimeException { String[] args = new String[] { "-Ajar=" + jarURL.toExternalForm() }; ArkContainer arkContainer = (ArkContainer) main(args); Assert.assertNotNull(ArkServiceLoader.getExtensionLoaderService()); arkContainer.stop(); } @Test public void testResponseMessage() { String[] args = new String[] { "-Ajar=" + jarURL.toExternalForm() }; main(args); ArkCommandHandler arkCommandHandler = new ArkCommandHandler(); assertEquals(TELNET_STRING_END + TELNET_SESSION_PROMPT, arkCommandHandler.responseMessage(null)); assertTrue(arkCommandHandler.responseMessage("a").endsWith( TELNET_STRING_END + TELNET_STRING_END + TELNET_SESSION_PROMPT)); assertTrue(arkCommandHandler.responseMessage("-a").endsWith( TELNET_STRING_END + TELNET_STRING_END + TELNET_SESSION_PROMPT)); assertTrue(arkCommandHandler.responseMessage("biz -a").endsWith( TELNET_STRING_END + TELNET_STRING_END + TELNET_SESSION_PROMPT)); } @Test public void testDeployBizAfterMasterBizReady() throws Exception { String[] args = new String[] { "-Ajar=" + jarURL.toExternalForm() }; ArkContainer arkContainer = (ArkContainer) main(args); BizManagerService bizManagerService = arkContainer.getArkServiceContainer().getService(BizManagerService.class); BizFactoryService bizFactoryService = arkContainer.getArkServiceContainer().getService(BizFactoryService.class); BizModel masterBiz = createTestBizModel("master", "1.0.0", BizState.RESOLVED, ArkContainerTest.class.getClassLoader()); ArkConfigs.setEmbedStaticBizEnable(true); try (MockedStatic mockedStatic = Mockito.mockStatic(ArkClient.class)) { mockedStatic.when(ArkClient::getMasterBiz).thenReturn(masterBiz); mockedStatic.when(ArkClient::getBizFactoryService).thenReturn(bizFactoryService); mockedStatic.when(ArkClient::getBizManagerService).thenReturn(bizManagerService); bizManagerService.registerBiz(masterBiz); masterBiz.setBizState(BizState.ACTIVATED); assertEquals(arkContainer, arkContainer.deployBizAfterMasterBizReady()); assertEquals(2,bizManagerService.getBizInOrder().size()); }finally { bizManagerService.getBizInOrder().stream().forEach(biz -> ((BizModel)biz).setBizState(BizState.BROKEN)); bizManagerService.unRegisterBiz("biz-demo","1.0.0"); arkContainer.stop(); ArkConfigs.setEmbedStaticBizEnable(false); } } @Test(expected = ArkRuntimeException.class) public void testStartNotWithCommandLine() { String[] args = new String[] { "-BmethodName=main", "-BclassName=a", "-Aclasspath=" + jarURL.toString() }; main(args); } @Test public void testOtherMethods() { String[] args = new String[] { "-Ajar=" + jarURL.toExternalForm() }; ArkContainer arkContainer = (ArkContainer) main(args); assertEquals(0, arkContainer.getProfileConfFiles("prod").size()); assertTrue(arkContainer.isRunning()); assertNotNull(arkContainer.getArkServiceContainer()); assertNotNull(arkContainer.getPipelineContext()); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/BaseTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.model.PluginModel; import com.alipay.sofa.ark.container.pipeline.RegisterServiceStage; import com.alipay.sofa.ark.container.registry.PluginServiceProvider; import com.alipay.sofa.ark.container.service.ArkServiceContainer; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.container.service.classloader.BizClassLoader; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.extension.ExtensionLoaderService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.mockito.MockedStatic; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.net.URL; import java.util.ArrayList; import java.util.List; import static com.alipay.sofa.ark.common.util.FileUtils.file; import static com.alipay.sofa.ark.spi.service.extension.ArkServiceLoader.setExtensionLoaderService; import static org.mockito.Mockito.*; /** * * @author ruoshan * @since 0.1.0 */ public class BaseTest { private URL jarURL = ArkContainerTest.class.getClassLoader() .getResource("test.jar"); protected ArkServiceContainer arkServiceContainer = new ArkServiceContainer(new String[] {}); protected ArkContainer arkContainer; MockedStatic managementFactoryMockedStatic; static { // fix cobertura bug new PluginServiceProvider(new PluginModel()); } public static BizModel createTestBizModel(String bizName, String bizVersion, BizState bizState, URL[] urls) { BizModel bizModel = new BizModel().setBizState(bizState); bizModel.setBizName(bizName).setBizVersion(bizVersion); BizClassLoader bizClassLoader = new BizClassLoader(bizModel.getIdentity(), urls); bizClassLoader.setBizModel(bizModel); bizModel.setClassPath(urls).setClassLoader(bizClassLoader); return bizModel; } public static BizModel createTestBizModel(String bizName, String bizVersion, BizState bizState, ClassLoader classLoader) { BizModel bizModel = new BizModel().setBizState(bizState); bizModel.setBizName(bizName).setBizVersion(bizVersion); bizModel.setClassLoader(classLoader); return bizModel; } @Before public void before() { List mockArguments = new ArrayList<>(); String filePath = this.getClass().getClassLoader().getResource("SampleClass.class").getPath(); String workingPath = file(filePath).getParent(); mockArguments.add(String.format("javaaget:%s", workingPath)); mockArguments.add(String.format("-javaagent:%s", workingPath)); mockArguments.add(String.format("-javaagent:%s=xx", workingPath)); RuntimeMXBean runtimeMXBean = mock(RuntimeMXBean.class); when(runtimeMXBean.getInputArguments()).thenReturn(mockArguments); when(runtimeMXBean.getName()).thenReturn(""); managementFactoryMockedStatic = mockStatic(ManagementFactory.class); managementFactoryMockedStatic.when(ManagementFactory::getRuntimeMXBean).thenReturn(runtimeMXBean); arkServiceContainer.start(); arkServiceContainer.getService(RegisterServiceStage.class).process(null); setExtensionLoaderService(arkServiceContainer.getService(ExtensionLoaderService.class)); } @After public void after() { arkServiceContainer.stop(); if (arkContainer != null) { arkContainer.stop(); } managementFactoryMockedStatic.close(); } @BeforeClass public static void beforeClass() { } protected void registerMockPlugin() { if (arkContainer == null) { String[] args = new String[] { "-Ajar=" + jarURL.toExternalForm() }; arkContainer = (ArkContainer) ArkContainer.main(args); } PluginManagerService pluginManagerService = ArkServiceContainerHolder.getContainer() .getService(PluginManagerService.class); Plugin plugin = new PluginModel().setPluginName("mock") .setPluginClassLoader(this.getClass().getClassLoader()).setImportClasses("") .setImportPackages("").setImportResources(""); pluginManagerService.registerPlugin(plugin); } protected void registerMockBiz() { if (arkContainer == null) { String[] args = new String[] { "-Ajar=" + jarURL.toExternalForm() }; arkContainer = (ArkContainer) ArkContainer.main(args); } BizManagerService bizManagerService = ArkServiceContainerHolder.getContainer().getService( BizManagerService.class); Biz biz = new BizModel().setBizName("mock").setBizVersion("1.0") .setClassLoader(this.getClass().getClassLoader()).setDenyImportPackages("") .setDenyImportClasses("").setDenyImportResources("").setBizState(BizState.RESOLVED); bizManagerService.registerBiz(biz); ((BizModel) biz).setBizState(BizState.ACTIVATED); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/ClassLoaderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.common.util.EnvironmentUtils; import com.alipay.sofa.ark.container.test.TestClassLoader; import com.alipay.sofa.ark.spi.constant.Constants; import org.junit.Assert; import org.junit.Test; import java.net.URLClassLoader; /** * @author qilong.zql * @since 3.1.0 */ public class ClassLoaderTest extends BaseTest { @Test public void testDefaultDelegate() throws Throwable { // compatible with JDK9+ ClassLoader classLoader = ClassLoader.getSystemClassLoader(); TestClassLoader testClassLoader = new TestClassLoader("mock:1.0", ClassLoaderUtils.getURLs(classLoader), classLoader); Assert.assertEquals(classLoader, testClassLoader.loadClass(Test.class.getCanonicalName()) .getClassLoader()); Assert.assertEquals(testClassLoader, testClassLoader.loadClass(ClassLoaderTest.class.getCanonicalName()).getClassLoader()); } @Test public void testDelegateConfigure() throws Throwable { EnvironmentUtils.setProperty(Constants.FORCE_DELEGATE_TO_TEST_CLASSLOADER, Test.class .getPackage().getName()); EnvironmentUtils.setProperty(Constants.FORCE_DELEGATE_TO_APP_CLASSLOADER, ClassLoaderTest.class.getPackage().getName()); // compatible with JDK9+ ClassLoader classLoader = ClassLoader.getSystemClassLoader(); TestClassLoader testClassLoader = new TestClassLoader("mock:1.0", ClassLoaderUtils.getURLs(classLoader), classLoader); Assert.assertEquals(testClassLoader, testClassLoader.loadClass(Test.class.getCanonicalName()).getClassLoader()); Assert.assertEquals(classLoader, testClassLoader.loadClass(ClassLoaderTest.class.getCanonicalName()).getClassLoader()); EnvironmentUtils.clearProperty(Constants.FORCE_DELEGATE_TO_APP_CLASSLOADER); EnvironmentUtils.clearProperty(Constants.FORCE_DELEGATE_TO_TEST_CLASSLOADER); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/model/BizModelTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.model; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.container.service.ArkServiceContainer; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.container.service.event.EventAdminServiceImpl; import com.alipay.sofa.ark.spi.event.biz.BeforeBizStopEvent; import com.alipay.sofa.ark.spi.model.BizInfo.BizStateRecord; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.HashSet; import java.util.List; import static com.alipay.sofa.ark.spi.constant.Constants.AUTO_UNINSTALL_WHEN_FAILED_ENABLE; import static com.alipay.sofa.ark.spi.constant.Constants.REMOVE_BIZ_INSTANCE_AFTER_STOP_FAILED; import static org.apache.commons.io.FileUtils.touch; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class BizModelTest { @Test public void testDoCheckDeclared() throws MalformedURLException { BizModel bizModel = new BizModel(); assertEquals(new HashSet(), bizModel.setAttribute("a", "b").setAttributes(new HashMap<>()) .getInjectExportPackages()); assertEquals(new HashSet(), bizModel.getInjectPluginDependencies()); bizModel.setCustomBizName("abc"); assertNotNull(bizModel.getAttributes()); assertNull(bizModel.getBizTempWorkDir()); bizModel.toString(); bizModel.setPluginClassPath(new URL[] { new URL("file://b/a.jar!/") }); assertTrue(bizModel.doCheckDeclared("file://b/a.jar!/b.jar")); assertTrue(bizModel.doCheckDeclared(this.getClass().getClassLoader() .getResource("test.jar").getPath())); } @Test public void testBizStateChanged() { BizModel bizModel = new BizModel(); bizModel.setBizName("biz1"); bizModel.setBizVersion("0.0.1-SNAPSHOT"); List changeLogs = bizModel.getBizStateRecords(); assertEquals(0, changeLogs.size()); // create Biz bizModel.setBizState(BizState.RESOLVED); bizModel.setClassLoader(this.getClass().getClassLoader()); assertEquals(1, changeLogs.size()); assertTrue(bizModel.toString().contains("-> resolved")); // activate Biz bizModel.setBizState(BizState.ACTIVATED); assertEquals(2, changeLogs.size()); assertTrue(bizModel.toString().contains("-> resolved")); assertTrue(bizModel.toString().contains("-> activated")); // deactivate Biz bizModel.setBizState(BizState.DEACTIVATED); assertEquals(3, changeLogs.size()); assertTrue(bizModel.toString().contains("-> resolved")); assertTrue(bizModel.toString().contains("-> activated")); assertTrue(bizModel.toString().contains("-> deactivated")); bizModel.setBizState(BizState.UNRESOLVED); assertEquals(4, changeLogs.size()); assertTrue(bizModel.toString().contains("-> resolved")); assertTrue(bizModel.toString().contains("-> activated")); assertTrue(bizModel.toString().contains("-> deactivated")); assertTrue(bizModel.toString().contains("-> unresolved")); } @Test public void testRecycleBizTempWorkDir() throws Throwable { assertFalse(BizModel.recycleBizTempWorkDir(null)); File fileJar = new File("/tmp/" + System.currentTimeMillis() + ".jar"); touch(fileJar); assertTrue(BizModel.recycleBizTempWorkDir(fileJar)); assertFalse(fileJar.exists()); File fileDir = new File("/tmp/" + System.currentTimeMillis() + "-test"); fileDir.mkdir(); File fileSubFile = new File(fileDir.getAbsolutePath() + "/subfile.jar"); touch(fileSubFile); assertTrue(BizModel.recycleBizTempWorkDir(fileDir)); assertFalse(fileDir.exists()); assertFalse(fileSubFile.exists()); } @Test public void testStopFailedWithClean() { BizModel bizModel = new BizModel(); bizModel.setBizName("biz1"); bizModel.setBizVersion("0.0.1-SNAPSHOT"); bizModel.setBizState(BizState.ACTIVATED); try (MockedStatic mockedStatic = Mockito.mockStatic(ArkServiceContainerHolder.class)) { EventAdminService eventAdminService = mock(EventAdminServiceImpl.class); BizManagerService bizManagerService = mock(BizManagerService.class); ArkServiceContainer arkServiceContainer = mock(ArkServiceContainer.class); doThrow(new RuntimeException()).when(eventAdminService).sendEvent(any(BeforeBizStopEvent.class)); when(arkServiceContainer.getService(EventAdminService.class)).thenReturn(eventAdminService); when(arkServiceContainer.getService(BizManagerService.class)).thenReturn(bizManagerService); mockedStatic.when(ArkServiceContainerHolder::getContainer).thenReturn(arkServiceContainer); try { bizModel.stop(); } catch (RuntimeException e) { } assertEquals(BizState.STOPPED, bizModel.getBizState()); } bizModel.setBizState(BizState.ACTIVATED); try (MockedStatic mockedStatic = Mockito.mockStatic(ArkServiceContainerHolder.class)) { ArkConfigs.putStringValue(REMOVE_BIZ_INSTANCE_AFTER_STOP_FAILED, "false"); EventAdminService eventAdminService = mock(EventAdminServiceImpl.class); BizManagerService bizManagerService = mock(BizManagerService.class); ArkServiceContainer arkServiceContainer = mock(ArkServiceContainer.class); doThrow(new RuntimeException()).when(eventAdminService).sendEvent(any(BeforeBizStopEvent.class)); when(arkServiceContainer.getService(EventAdminService.class)).thenReturn(eventAdminService); when(arkServiceContainer.getService(BizManagerService.class)).thenReturn(bizManagerService); mockedStatic.when(ArkServiceContainerHolder::getContainer).thenReturn(arkServiceContainer); try { bizModel.stop(); } catch (RuntimeException e) { } assertEquals(BizState.BROKEN, bizModel.getBizState()); } finally { ArkConfigs.putStringValue(REMOVE_BIZ_INSTANCE_AFTER_STOP_FAILED, "true"); } } @Test public void testStopSucceedWithClean() { BizModel bizModel = new BizModel(); bizModel.setBizName("biz1"); bizModel.setBizVersion("0.0.1-SNAPSHOT"); bizModel.setBizState(BizState.ACTIVATED); try (MockedStatic mockedStatic = Mockito.mockStatic(ArkServiceContainerHolder.class)) { EventAdminService eventAdminService = mock(EventAdminService.class); BizManagerService bizManagerService = mock(BizManagerService.class); ArkServiceContainer arkServiceContainer = mock(ArkServiceContainer.class); when(arkServiceContainer.getService(EventAdminService.class)).thenReturn(eventAdminService); when(arkServiceContainer.getService(BizManagerService.class)).thenReturn(bizManagerService); mockedStatic.when(ArkServiceContainerHolder::getContainer).thenReturn(arkServiceContainer); bizModel.stop(); assertEquals(BizState.STOPPED, bizModel.getBizState()); } bizModel.setBizState(BizState.ACTIVATED); try (MockedStatic mockedStatic = Mockito.mockStatic(ArkServiceContainerHolder.class)) { ArkConfigs.putStringValue(REMOVE_BIZ_INSTANCE_AFTER_STOP_FAILED, "false"); EventAdminService eventAdminService = mock(EventAdminService.class); BizManagerService bizManagerService = mock(BizManagerService.class); ArkServiceContainer arkServiceContainer = mock(ArkServiceContainer.class); when(arkServiceContainer.getService(EventAdminService.class)).thenReturn(eventAdminService); when(arkServiceContainer.getService(BizManagerService.class)).thenReturn(bizManagerService); mockedStatic.when(ArkServiceContainerHolder::getContainer).thenReturn(arkServiceContainer); bizModel.stop(); assertEquals(BizState.STOPPED, bizModel.getBizState()); } finally { ArkConfigs.putStringValue(AUTO_UNINSTALL_WHEN_FAILED_ENABLE, "true"); } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStageTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.pipeline; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.loader.DirectoryBizArchive; import com.alipay.sofa.ark.loader.JarBizArchive; import com.alipay.sofa.ark.spi.archive.BizArchive; import com.alipay.sofa.ark.spi.archive.ExecutableArchive; import com.alipay.sofa.ark.spi.archive.PluginArchive; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.pipeline.PipelineContext; import com.alipay.sofa.ark.spi.service.biz.BizFactoryService; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.plugin.PluginFactoryService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Field; import java.util.HashSet; import java.util.jar.Attributes; import java.util.jar.Manifest; import static com.alipay.sofa.ark.api.ArkConfigs.getStringValue; import static com.alipay.sofa.ark.api.ArkConfigs.setSystemProperty; import static com.alipay.sofa.ark.spi.constant.Constants.CONFIG_SERVER_ADDRESS; import static com.alipay.sofa.ark.spi.constant.Constants.MASTER_BIZ; import static java.util.Arrays.asList; import static org.mockito.Mockito.*; public class HandleArchiveStageTest { private String originalConfigServerAddress; private String originalMasterBiz; private BizFactoryService bizFactoryService = mock(BizFactoryService.class); private BizManagerService bizManagerService = mock(BizManagerService.class); private PluginFactoryService pluginFactoryService = mock(PluginFactoryService.class); private PluginManagerService pluginManagerService = mock(PluginManagerService.class); private PipelineContext pipelineContext = mock(PipelineContext.class); private ExecutableArchive executableArchive = mock(ExecutableArchive.class); private HandleArchiveStage handleArchiveStage = new HandleArchiveStage(); @Before public void before() throws Exception { originalConfigServerAddress = getStringValue(CONFIG_SERVER_ADDRESS); originalMasterBiz = getStringValue(MASTER_BIZ); when(pipelineContext.getExecutableArchive()).thenReturn(executableArchive); Field field = HandleArchiveStage.class.getDeclaredField("bizFactoryService"); field.setAccessible(true); field.set(handleArchiveStage, bizFactoryService); field = HandleArchiveStage.class.getDeclaredField("bizManagerService"); field.setAccessible(true); field.set(handleArchiveStage, bizManagerService); field = HandleArchiveStage.class.getDeclaredField("pluginFactoryService"); field.setAccessible(true); field.set(handleArchiveStage, pluginFactoryService); field = HandleArchiveStage.class.getDeclaredField("pluginManagerService"); field.setAccessible(true); field.set(handleArchiveStage, pluginManagerService); } @After public void after() { setSystemProperty(CONFIG_SERVER_ADDRESS, originalConfigServerAddress != null ? originalConfigServerAddress : ""); setSystemProperty(MASTER_BIZ, originalMasterBiz != null ? originalMasterBiz : "'"); clearInvocations(bizFactoryService, bizManagerService, pluginFactoryService, pluginManagerService); } @Test public void testProcess() throws Exception { Manifest manifest = mock(Manifest.class); BizArchive bizArchive1 = mock(DirectoryBizArchive.class); when(bizArchive1.getManifest()).thenReturn(manifest); BizArchive bizArchive2 = mock(JarBizArchive.class); when(bizArchive2.getManifest()).thenReturn(manifest); when(manifest.getMainAttributes()).thenReturn(mock(Attributes.class)); BizArchive bizArchive3 = mock(BizArchive.class); when(bizArchive3.getManifest()).thenReturn(manifest); BizArchive bizArchive4 = mock(BizArchive.class); when(bizArchive4.getManifest()).thenReturn(manifest); when(executableArchive.getBizArchives()).thenReturn( asList(bizArchive1, bizArchive2, bizArchive3, bizArchive4)); BizModel bizModel1 = new BizModel(); bizModel1.setBizName("a"); when(bizFactoryService.createBiz(bizArchive1)).thenReturn(bizModel1); when(bizFactoryService.createBiz(bizArchive2)).thenReturn(bizModel1); when(bizFactoryService.createBiz(bizArchive3)).thenReturn(bizModel1); BizModel bizModel2 = new BizModel(); bizModel2.setBizName("b"); when(bizFactoryService.createBiz(bizArchive4)).thenReturn(bizModel2); PluginArchive pluginArchive = mock(PluginArchive.class); when(executableArchive.getPluginArchives()).thenReturn(asList(pluginArchive)); when(bizManagerService.getBizInOrder()).thenReturn(asList(bizModel1)); Plugin plugin = mock(Plugin.class); when(pluginFactoryService.createPlugin(pluginArchive, null, new HashSet<>())).thenReturn( plugin); setSystemProperty(CONFIG_SERVER_ADDRESS, "localhost"); setSystemProperty(MASTER_BIZ, "a"); handleArchiveStage.process(pipelineContext); verify(bizFactoryService, times(4)).createBiz((BizArchive) any()); verify(bizManagerService, times(3)).registerBiz(any()); verify(bizManagerService, times(1)).getBizInOrder(); verify(pluginFactoryService, times(1)).createPlugin(pluginArchive, null, new HashSet<>()); verify(pluginManagerService, times(1)).registerPlugin(plugin); when(executableArchive.getBizArchives()).thenReturn(asList(bizArchive3)); setSystemProperty(CONFIG_SERVER_ADDRESS, ""); setSystemProperty(MASTER_BIZ, ""); handleArchiveStage.process(pipelineContext); verify(bizFactoryService, times(5)).createBiz((BizArchive) any()); verify(bizManagerService, times(4)).registerBiz(any()); verify(bizManagerService, times(2)).getBizInOrder(); } @Test public void testProcessStaticBizFromClasspath() throws Exception { BizArchive bizArchive = mock(BizArchive.class); when(executableArchive.getBizArchives()).thenReturn(asList(bizArchive)); handleArchiveStage.processStaticBizFromClasspath(pipelineContext); verify(bizFactoryService, times(1)).createBiz((bizArchive)); verify(bizManagerService, times(1)).registerBiz(null); } @Test public void testProcessEmbed() throws Exception { PluginArchive pluginArchive = mock(PluginArchive.class); when(executableArchive.getPluginArchives()).thenReturn(asList(pluginArchive)); Plugin plugin = mock(Plugin.class); when( pluginFactoryService.createEmbedPlugin(pluginArchive, pipelineContext.getClass() .getClassLoader())).thenReturn(plugin); BizModel bizModel = new BizModel(); bizModel.setBizName("a"); when(bizFactoryService.createEmbedMasterBiz(pipelineContext.getClass().getClassLoader())) .thenReturn(bizModel); handleArchiveStage.processEmbed(pipelineContext); verify(pluginFactoryService, times(1)).createEmbedPlugin(pluginArchive, pipelineContext.getClass().getClassLoader()); verify(pluginManagerService, times(1)).registerPlugin(plugin); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/ArkServiceContainerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.pipeline.StandardPipeline; import com.alipay.sofa.ark.spi.pipeline.Pipeline; import org.junit.Assert; import org.junit.Test; /** * @author ruoshan * @since 0.1.0 */ public class ArkServiceContainerTest extends BaseTest { @Test public void testStart() { arkServiceContainer.start(); Assert.assertTrue(arkServiceContainer.isStarted()); Assert.assertTrue(arkServiceContainer.isRunning()); } @Test public void testStop() { arkServiceContainer.start(); arkServiceContainer.stop(); Assert.assertFalse(arkServiceContainer.isRunning()); } @Test public void testGetService() { arkServiceContainer.start(); Pipeline pipeline = arkServiceContainer.getService(Pipeline.class); Assert.assertNotNull(pipeline); Assert.assertTrue(pipeline instanceof StandardPipeline); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/api/ArkClientTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.api; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.api.ClientResponse; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.service.biz.BizManagerServiceImpl; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.loader.JarBizArchive; import com.alipay.sofa.ark.loader.archive.JarFileArchive; import com.alipay.sofa.ark.loader.jar.JarFile; import com.alipay.sofa.ark.spi.archive.BizArchive; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.ArkEvent; import com.alipay.sofa.ark.spi.model.*; import com.alipay.sofa.ark.spi.replay.Replay; import com.alipay.sofa.ark.spi.service.biz.BizFactoryService; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import com.alipay.sofa.ark.spi.service.event.EventHandler; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import static com.alipay.sofa.ark.api.ArkClient.*; import static com.alipay.sofa.ark.api.ResponseCode.*; import static com.alipay.sofa.ark.common.util.FileUtils.copyInputStreamToFile; import static com.alipay.sofa.ark.spi.constant.Constants.ACTIVATE_NEW_MODULE; import static com.alipay.sofa.ark.spi.constant.Constants.AUTO_UNINSTALL_WHEN_FAILED_ENABLE; import static com.alipay.sofa.ark.spi.constant.Constants.CONFIG_BIZ_URL; import static com.alipay.sofa.ark.spi.constant.Constants.EMBED_ENABLE; import static com.alipay.sofa.ark.spi.constant.Constants.ACTIVATE_MULTI_BIZ_VERSION_ENABLE; import static com.alipay.sofa.ark.spi.model.BizOperation.OperationType.CHECK; import static com.alipay.sofa.ark.spi.model.BizOperation.OperationType.INSTALL; import static com.alipay.sofa.ark.spi.model.BizOperation.OperationType.SWITCH; import static com.alipay.sofa.ark.spi.model.BizOperation.OperationType.UNINSTALL; import static com.alipay.sofa.ark.spi.model.BizState.ACTIVATED; import static com.alipay.sofa.ark.spi.model.BizState.DEACTIVATED; import static com.alipay.sofa.ark.spi.model.BizState.RESOLVED; import static java.lang.System.setProperty; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author qilong.zql * @since 0.6.0 */ public class ArkClientTest extends BaseTest { // bizName=biz-demo, bizVersion=1.0.0 private URL bizUrl1; // bizName=biz-demo, bizVersion=2.0.0 private URL bizUrl2; // bizName=biz-demo, bizVersion=3.0.0 private URL bizUrl3; // bizName=biz-demo, bizVersion=4.0.0 private URL bizUrl4; // bizName=biz-demo, bizVersion=5.0.0 private URL bizUrl5; // samplePlugin private URL samplePlugin; @Before public void before() { super.before(); // bizName=biz-demo, bizVersion=1.0.0 bizUrl1 = this.getClass().getClassLoader().getResource("sample-ark-1.0.0-ark-biz.jar"); // bizName=biz-demo, bizVersion=2.0.0 bizUrl2 = this.getClass().getClassLoader().getResource("sample-ark-2.0.0-ark-biz.jar"); // bizName=biz-demo, bizVersion=3.0.0 bizUrl3 = this.getClass().getClassLoader().getResource("sample-ark-3.0.0-ark-biz.jar"); // bizName=biz-demo, bizVersion=4.0.0 bizUrl4 = this.getClass().getClassLoader().getResource("sample-ark-4.0.0-ark-biz.jar"); // bizName=biz-demo, bizVersion=5.0.0 bizUrl5 = this.getClass().getClassLoader().getResource("sample-ark-5.0.0-ark-biz.jar"); // samplePlugin samplePlugin = this.getClass().getClassLoader().getResource("sample-plugin.jar"); } @Test public void testCreateBizSaveFile() { File bizFile = createBizSaveFile("test-biz-demo", "1.0.0", "suffix"); assertTrue(bizFile.getName().contains("test-biz-demo-1.0.0-suffix")); } @Test public void testInstallBiz() throws Throwable { ClientResponse response = checkBiz(); assertEquals(SUCCESS, response.getCode()); assertEquals(0, response.getBizInfos().size()); // test install File bizFile = createBizSaveFile("biz-demo", "1.0.0"); copyInputStreamToFile(bizUrl1.openStream(), bizFile); response = installBiz(bizFile); assertEquals(SUCCESS, response.getCode()); BizInfo bizInfo = response.getBizInfos().iterator().next(); assertEquals(ACTIVATED, bizInfo.getBizState()); // test install biz with same bizName and bizVersion // test install File bizFile1 = createBizSaveFile("biz-demo", "1.0.0"); copyInputStreamToFile(bizUrl1.openStream(), bizFile1); response = installBiz(bizFile1); assertEquals(REPEAT_BIZ, response.getCode()); // test install biz with same bizName and different bizVersion // response = ArkClient.installBiz(new File(bizUrl2.getFile())); File bizFile2 = createBizSaveFile("biz-demo", "2.0.0"); copyInputStreamToFile(bizUrl2.openStream(), bizFile2); response = installBiz(bizFile2); assertEquals(SUCCESS, response.getCode()); bizInfo = response.getBizInfos().iterator().next(); assertEquals(DEACTIVATED, bizInfo.getBizState()); // test install biz with same bizName and different bizVersion and active latest setProperty(ACTIVATE_NEW_MODULE, "true"); setProperty(EMBED_ENABLE, "true"); File bizFile3 = createBizSaveFile("biz-demo", "3.0.0"); copyInputStreamToFile(bizUrl3.openStream(), bizFile3); response = installBiz(bizFile3); setProperty(ACTIVATE_NEW_MODULE, ""); setProperty(EMBED_ENABLE, ""); assertEquals(SUCCESS, response.getCode()); bizInfo = response.getBizInfos().iterator().next(); assertEquals(ACTIVATED, bizInfo.getBizState()); // test install biz with same bizName and different bizVersion and keep old module state setProperty(ACTIVATE_MULTI_BIZ_VERSION_ENABLE, "true"); File bizFile4 = createBizSaveFile("biz-demo", "4.0.0"); copyInputStreamToFile(bizUrl4.openStream(), bizFile4); response = installBiz(bizFile4); assertEquals(SUCCESS, response.getCode()); BizManagerService bizManagerService = arkServiceContainer .getService(BizManagerService.class); assertSame(bizManagerService.getBiz("biz-demo", "3.0.0").getBizState(), ACTIVATED); setProperty(ACTIVATE_MULTI_BIZ_VERSION_ENABLE, ""); } @Test public void testBizArguments() throws Throwable { EventAdminService eventAdminService = arkServiceContainer .getService(EventAdminService.class); List topicList = new ArrayList<>(); EventHandler eventHandler = new EventHandler() { @Override public void handleEvent(ArkEvent event) { topicList.add(event.getTopic()); } @Override public int getPriority() { return 0; } }; eventAdminService.register(eventHandler); File bizFile3 = createBizSaveFile("biz-demo", "3.0.0"); copyInputStreamToFile(bizUrl3.openStream(), bizFile3); installBiz(bizFile3); // ArkClient.installBiz(new File(bizUrl3.getFile())); uninstallBiz("biz-demo", "3.0.0"); File bizFile33 = createBizSaveFile("biz-demo", "3.0.0"); copyInputStreamToFile(bizUrl3.openStream(), bizFile33); installBiz(bizFile33, new String[] { "demo" }); uninstallBiz("biz-demo", "3.0.0"); assertEquals("BEFORE-INVOKE-BIZ-START", topicList.get(0)); assertEquals("No arguments", topicList.get(1)); assertEquals("AFTER-INVOKE-BIZ-START", topicList.get(2)); // new event assertEquals("BEFORE-RECYCLE-BIZ", topicList.get(4)); assertEquals("demo", topicList.get(7)); } @Test public void testCheckBiz() throws Throwable { testInstallBiz(); // test check all biz ClientResponse response = checkBiz(); assertEquals(SUCCESS, response.getCode()); assertEquals(4, response.getBizInfos().size()); // test check specified bizName response = checkBiz("biz-demo"); assertEquals(SUCCESS, response.getCode()); assertEquals(4, response.getBizInfos().size()); // test check specified bizName and version response = checkBiz("biz-demo", "2.0.0"); assertEquals(SUCCESS, response.getCode()); assertEquals(1, response.getBizInfos().size()); response = checkBiz("biz-demo", "3.0.0"); assertEquals(SUCCESS, response.getCode()); assertEquals(1, response.getBizInfos().size()); response = checkBiz("biz-demo", "4.0.0"); assertEquals(SUCCESS, response.getCode()); assertEquals(1, response.getBizInfos().size()); response = checkBiz("biz-demo", "5.0.0"); assertEquals(SUCCESS, response.getCode()); assertEquals(0, response.getBizInfos().size()); } @Test public void testUninstallBiz() throws Throwable { testCheckBiz(); // test uninstall biz ClientResponse response = uninstallBiz("biz-demo", "1.0.0"); assertEquals(SUCCESS, response.getCode()); // test check all biz response = checkBiz(); assertEquals(SUCCESS, response.getCode()); assertEquals(3, response.getBizInfos().size()); } @Test public void testUninstallBizWhenIncludeLib() throws Throwable { testCheckBiz(); // test uninstall biz ClientResponse response = uninstallBiz("biz-demo", "3.0.0"); assertEquals(SUCCESS, response.getCode()); // test check all biz response = checkBiz(); assertEquals(SUCCESS, response.getCode()); assertEquals(3, response.getBizInfos().size()); } @Test public void testInstallBizWithThrowable() throws Throwable { File bizFile = createBizSaveFile("biz-demo", "1.0.0"); copyInputStreamToFile(bizUrl1.openStream(), bizFile); BizFactoryService bizFactoryService = getBizFactoryService(); BizFactoryService bizFactoryServiceMock = mock(BizFactoryService.class); setBizFactoryService(bizFactoryServiceMock); Biz biz = mock(Biz.class); doThrow(new IllegalArgumentException()).when(biz).start(any()); when(bizFactoryServiceMock.createBiz((File) any(), (BizConfig) any())).thenReturn(biz); try { installBiz(bizFile, new BizConfig()); assertTrue(false); } catch (Throwable e) { setBizFactoryService(bizFactoryService); assertEquals(IllegalArgumentException.class, e.getClass()); } assertNotNull(getBizManagerService()); assertNotNull(getBizFactoryService()); assertNotNull(getPluginManagerService()); assertNotNull(getArguments()); } @Test public void testInstallOperation() throws Throwable { BizOperation bizOperation = new BizOperation(); bizOperation.setOperationType(INSTALL); bizOperation.getParameters().put(CONFIG_BIZ_URL, bizUrl1.toString()); bizOperation.setBizName("biz-demo"); bizOperation.setBizVersion("1.0.0"); ClientResponse response = installOperation(bizOperation, new String[] {}); assertEquals(SUCCESS, response.getCode()); } @Test public void testInstallOperationWithDynamicMainClass() throws Throwable { // the biz module will start with dynamic mainClass specified in env parameters, which is org.example.Main2 BizOperation bizOperation = new BizOperation(); bizOperation.setOperationType(INSTALL); bizOperation.getParameters().put(CONFIG_BIZ_URL, bizUrl5.toString()); bizOperation.setBizName("biz-demo"); bizOperation.setBizVersion("5.0.0"); Map envs = Collections.singletonMap(Constants.BIZ_MAIN_CLASS, "org.example.Main2"); ClientResponse response2 = installOperation(bizOperation, new String[] {}, envs); assertEquals(SUCCESS, response2.getCode()); assertEquals("org.example.Main2", (new ArrayList<>(response2.getBizInfos())).get(0) .getMainClass()); // but in fact, the biz module was packaged with mainClass as org.example.Main1 URL url = new URL(bizOperation.getParameters().get(Constants.CONFIG_BIZ_URL)); File file = ArkClient.createBizSaveFile(bizOperation.getBizName(), bizOperation.getBizVersion()); try (InputStream inputStream = url.openStream()) { FileUtils.copyInputStreamToFile(inputStream, file); } JarFile bizFile = new JarFile(file); JarFileArchive jarFileArchive = new JarFileArchive(bizFile); BizArchive bizArchive = new JarBizArchive(jarFileArchive); assertEquals("org.example.Main1", bizArchive.getManifest().getMainAttributes().getValue(Constants.MAIN_CLASS_ATTRIBUTE)); assertEquals("org.example.Main1", bizArchive.getManifest().getMainAttributes().getValue(Constants.START_CLASS_ATTRIBUTE)); } @Test public void testInstallBizFailed() throws Throwable { File bizFile = createBizSaveFile("biz-install-failed-demo", "1.0.0"); copyInputStreamToFile(bizUrl1.openStream(), bizFile); BizFactoryService bizFactoryService = getBizFactoryService(); BizFactoryService bizFactoryServiceMock = mock(BizFactoryService.class); BizManagerService bizManagerService = getBizManagerService(); BizManagerServiceImpl bizManagerServiceMock = new BizManagerServiceImpl(); Biz biz = mock(Biz.class); when(biz.getIdentity()).thenReturn("biz-install-failed-demo:1.0.0"); when(biz.getBizState()).thenReturn(RESOLVED); when(biz.getBizName()).thenReturn("biz-install-failed-demo"); when(biz.getBizVersion()).thenReturn("1.0.0"); doThrow(new IllegalArgumentException()).when(biz).start(any(), any()); when(bizFactoryServiceMock.createBiz((File) any(), (BizConfig) any())).thenReturn(biz); // case1: not set AUTO_UNINSTALL_ENABLE try { setBizFactoryService(bizFactoryServiceMock); setBizManagerService(bizManagerServiceMock); doThrow(new Exception()).when(biz).stop(); installBiz(bizFile, new BizConfig()); fail(); } catch (Throwable e) { assertFalse(bizManagerServiceMock.getBiz("biz-install-failed-demo").isEmpty()); } finally { setBizFactoryService(bizFactoryService); setBizManagerService(bizManagerService); } // case2: set AUTO_UNINSTALL_ENABLE=false try { ArkConfigs.putStringValue(AUTO_UNINSTALL_WHEN_FAILED_ENABLE, "false"); setBizFactoryService(bizFactoryServiceMock); setBizManagerService(bizManagerServiceMock); installBiz(bizFile, new BizConfig()); fail(); } catch (Throwable e) { assertFalse(bizManagerServiceMock.getBiz("biz-install-failed-demo").isEmpty()); setBizFactoryService(bizFactoryService); setBizManagerService(bizManagerService); } finally { ArkConfigs.putStringValue(AUTO_UNINSTALL_WHEN_FAILED_ENABLE, "true"); } } @Test public void testUninstallOperation() throws Throwable { BizOperation bizOperation = new BizOperation(); bizOperation.setOperationType(INSTALL); bizOperation.getParameters().put(CONFIG_BIZ_URL, bizUrl1.toString()); bizOperation.setBizName("biz-demo"); bizOperation.setBizVersion("1.0.0"); installOperation(bizOperation, new String[] {}); bizOperation.setOperationType(UNINSTALL); ClientResponse response = uninstallOperation(bizOperation); assertEquals(SUCCESS, response.getCode()); } @Test public void testSwitchOperation() throws Throwable { BizOperation bizOperation = new BizOperation(); bizOperation.setOperationType(INSTALL); bizOperation.getParameters().put(CONFIG_BIZ_URL, bizUrl1.toString()); bizOperation.setBizName("biz-demo"); bizOperation.setBizVersion("1.0.0"); installOperation(bizOperation, new String[] {}); bizOperation.setOperationType(SWITCH); ClientResponse response = switchOperation(bizOperation); assertEquals(SUCCESS, response.getCode()); } @Test public void testCheckOperation() throws Throwable { BizOperation bizOperation = new BizOperation(); bizOperation.setOperationType(INSTALL); bizOperation.getParameters().put(CONFIG_BIZ_URL, bizUrl1.toString()); bizOperation.setBizName("biz-demo"); bizOperation.setBizVersion("1.0.0"); installOperation(bizOperation, new String[] {}); bizOperation.setOperationType(CHECK); ClientResponse response = checkOperation(bizOperation); assertEquals(SUCCESS, response.getCode()); bizOperation.setBizVersion("2.0.0"); response = checkOperation(bizOperation); assertEquals(SUCCESS, response.getCode()); } @Test public void testInvocationReplay() throws Throwable { assertEquals("1", invocationReplay("1.0.0", new Replay() { @Override public Object invoke() { return "1"; } })); } @Test public void testInstallPlugin() throws Throwable { PluginOperation pluginOperation = new PluginOperation(); pluginOperation.setPluginName("plugin-demo"); ClientResponse clientResponse = installPlugin(pluginOperation); assertEquals(FAILED, clientResponse.getCode()); pluginOperation.setLocalFile(new File(samplePlugin.toURI())); try { installPlugin(pluginOperation); } catch (Exception exception) { assertTrue(exception instanceof ArkRuntimeException); } clientResponse = checkPlugin(); assertEquals(1, clientResponse.getPluginInfos().size()); clientResponse = checkPlugin("plugin-demo"); assertEquals(1, clientResponse.getPluginInfos().size()); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/biz/BizCommandProviderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.biz; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.service.biz.BizCommandProvider.BizCommand; import com.alipay.sofa.ark.container.session.handler.ArkCommandHandler; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.service.biz.BizDeployService; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.injection.InjectionService; import org.junit.Test; import java.net.MalformedURLException; import java.net.URL; import static com.alipay.sofa.ark.api.ArkConfigs.putStringValue; import static com.alipay.sofa.ark.container.service.biz.BizCommandProvider.HELP_MESSAGE; import static com.alipay.sofa.ark.spi.constant.Constants.MASTER_BIZ; import static com.alipay.sofa.ark.spi.model.BizState.ACTIVATED; import static com.alipay.sofa.ark.spi.model.BizState.DEACTIVATED; import static org.junit.Assert.*; /** * @author qilong.zql * @since 0.6.0 */ public class BizCommandProviderTest extends BaseTest { private BizManagerService bizManagerService; private InjectionService injectionService; private BizCommandProvider bizCommandProvider; private BizDeployService bizDeployService; @Override public void before() { super.before(); bizManagerService = arkServiceContainer.getService(BizManagerService.class); injectionService = arkServiceContainer.getService(InjectionService.class); bizDeployService = arkServiceContainer.getService(BizDeployService.class); mockBiz(); bizDeployService.deploy(new String[] {}); bizCommandProvider = new BizCommandProvider(); injectionService.inject(bizCommandProvider); // trigger telnet command thread pool to be created new ArkCommandHandler(); } @Test public void testBizCommandPattern() { assertFalse(bizCommandProvider.validate("biz")); assertFalse(bizCommandProvider.validate("biz -m")); assertFalse(bizCommandProvider.validate("biz -d")); assertFalse(bizCommandProvider.validate("biz -s")); assertFalse(bizCommandProvider.validate("biz -i")); assertFalse(bizCommandProvider.validate("biz -u")); assertFalse(bizCommandProvider.validate("biz -o")); assertTrue(bizCommandProvider.validate("biz -a")); assertTrue(bizCommandProvider.validate("biz -h")); assertFalse(bizCommandProvider.validate("biz -ah")); assertFalse(bizCommandProvider.validate("biz -am A1:V1")); assertFalse(bizCommandProvider.validate("biz -hm A1:V1")); assertFalse(bizCommandProvider.validate("biz -mi A1:V1")); assertFalse(bizCommandProvider.validate("biz -mu A1:V1")); assertFalse(bizCommandProvider.validate("biz -mo A1:V1")); assertTrue(bizCommandProvider.validate("biz -msd A1:V1")); assertTrue(bizCommandProvider.validate("biz -msd A1:V1 A2:V2")); assertFalse(bizCommandProvider.validate("biz -io A1:V1")); assertFalse(bizCommandProvider.validate("biz -i A1:V1 A2:V2")); assertTrue(bizCommandProvider.validate("biz -i A1:V1")); assertFalse(bizCommandProvider.validate("biz -u A1:V1 A2:V2")); assertTrue(bizCommandProvider.validate("biz -u A1:V1")); assertFalse(bizCommandProvider.validate("biz -o A1:V1 A2:V2")); assertTrue(bizCommandProvider.validate("biz -o A1:V1")); } @Test public void testBizInfo() { String multiBizInfo = bizCommandProvider.handleCommand("biz -m A1:V1 A1:V2"); String multiOptionBizInfo = bizCommandProvider.handleCommand("biz -md A1:V1 B1:V1"); assertTrue(multiBizInfo.contains("MainClassA1")); assertTrue(multiBizInfo.contains("MainClassA2")); assertFalse(multiBizInfo.contains("ClassLoader")); assertFalse(multiBizInfo.contains("ClassPath")); assertFalse(multiBizInfo.contains("MainClassB1")); assertTrue(multiOptionBizInfo.contains("MainClassA1")); assertTrue(multiOptionBizInfo.contains("MainClassB1")); assertFalse(multiOptionBizInfo.contains("MainClassA2")); assertTrue(multiOptionBizInfo.contains("ClassLoader")); assertTrue(multiOptionBizInfo.contains("ClassPath")); } @Test public void testInstallBiz() { String msg = bizCommandProvider.handleCommand("biz -i C1:V1"); assertTrue(msg.contains("Exists some biz")); ((MockBiz) bizManagerService.getBizByIdentity("A1:V1")).setBizState(ACTIVATED); ((MockBiz) bizManagerService.getBizByIdentity("A1:V2")).setBizState(DEACTIVATED); ((MockBiz) bizManagerService.getBizByIdentity("B1:V1")).setBizState(ACTIVATED); msg = bizCommandProvider.handleCommand("biz -i C1:V1"); assertTrue(msg.contains("Start to process install command now, pls wait and check.")); } @Test public void testSwitchBiz() { Biz bizA1 = ((MockBiz) bizManagerService.getBizByIdentity("A1:V1")).setBizState(ACTIVATED); Biz bizA2 = ((MockBiz) bizManagerService.getBizByIdentity("A1:V2")) .setBizState(DEACTIVATED); Biz bizB1 = ((MockBiz) bizManagerService.getBizByIdentity("B1:V1")).setBizState(ACTIVATED); bizCommandProvider.handleCommand("biz -o A1:V2"); sleep(200); assertTrue(bizA1.getBizState().equals(DEACTIVATED)); assertTrue(bizA2.getBizState().equals(ACTIVATED)); assertTrue(bizB1.getBizState().equals(ACTIVATED)); } @Test public void testUninstallBiz() { Biz bizA1 = ((MockBiz) bizManagerService.getBizByIdentity("A1:V1")).setBizState(ACTIVATED); Biz bizA2 = ((MockBiz) bizManagerService.getBizByIdentity("A1:V2")) .setBizState(DEACTIVATED); Biz bizB1 = ((MockBiz) bizManagerService.getBizByIdentity("B1:V1")).setBizState(ACTIVATED); bizCommandProvider.handleCommand("biz -u B1:V1"); sleep(200); assertTrue(bizA1.getBizState().equals(ACTIVATED)); assertTrue(bizA2.getBizState().equals(DEACTIVATED)); assertNull(bizManagerService.getBizByIdentity("B1:V1")); } @Test public void testUninstallMasterBiz() { putStringValue(MASTER_BIZ, "B1"); Biz bizA1 = ((MockBiz) bizManagerService.getBizByIdentity("A1:V1")).setBizState(ACTIVATED); Biz bizA2 = ((MockBiz) bizManagerService.getBizByIdentity("A1:V2")) .setBizState(DEACTIVATED); Biz bizB1 = ((MockBiz) bizManagerService.getBizByIdentity("B1:V1")).setBizState(ACTIVATED); bizCommandProvider.handleCommand("biz -u B1:V1"); sleep(200); assertTrue(bizA1.getBizState().equals(ACTIVATED)); assertTrue(bizA2.getBizState().equals(DEACTIVATED)); assertTrue(bizB1.getBizState().equals(ACTIVATED)); // assertNotNull(bizManagerService.getBizByIdentity("B1:V1")); } private void mockBiz() { MockBiz bizA1 = new MockBiz(); bizA1.setBizName("A1").setBizVersion("V1").setWebContextPath("/A1") .setBizState(BizState.RESOLVED).setMainClass("MainClassA1"); MockBiz bizA2 = new MockBiz(); bizA2.setBizName("A1").setBizVersion("V2").setWebContextPath("/A2") .setBizState(BizState.RESOLVED).setMainClass("MainClassA2"); MockBiz bizB1 = new MockBiz(); bizB1.setBizName("B1").setBizVersion("V1").setWebContextPath("/B1") .setBizState(BizState.RESOLVED).setMainClass("MainClassB1"); bizManagerService.registerBiz(bizA1); bizManagerService.registerBiz(bizA2); bizManagerService.registerBiz(bizB1); } private void sleep(long mill) { try { Thread.sleep(mill); } catch (Throwable t) { // ignore } } class MockBiz extends BizModel { @Override public void start(String[] args) throws Throwable { } @Override public void stop() { // just to mock stop Biz biz = bizManagerService.getBiz(this.getBizName(), this.getBizVersion()); if (biz.getBizState() != BizState.RESOLVED) { bizManagerService.unRegisterBiz(this.getBizName(), this.getBizVersion()); } } } @Test public void testBizCommandInvalidate() throws MalformedURLException { BizCommand bizCommand = bizCommandProvider.new BizCommand(""); assertFalse(bizCommand.isValidate()); bizCommand = bizCommandProvider.new BizCommand("biz -"); assertFalse(bizCommand.isValidate()); bizCommand = bizCommandProvider.new BizCommand("biz -x"); assertFalse(bizCommand.isValidate()); bizCommand = bizCommandProvider.new BizCommand("biz -h a"); assertFalse(bizCommand.isValidate()); assertTrue(bizCommand.process().startsWith("Error command format")); bizCommand = bizCommandProvider.new BizCommand("biz -h"); assertEquals(HELP_MESSAGE, bizCommand.process()); mockBiz(); bizCommand = bizCommandProvider.new BizCommand("biz -a"); assertEquals("A1:V1:resolved\nA1:V2:resolved\nB1:V1:resolved\nbiz count = 3\n", bizCommand.process()); assertTrue(bizCommand.bizInfo("a:b").startsWith("Invalid bizIdentity: ")); String bizCommandStr = bizCommand.join( new URL[] { new URL("file:\\a"), new URL("file:\\b") }, "&"); assertTrue(bizCommandStr.equals("\\a&\\b") || bizCommandStr.equals("/a&/b")); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/biz/BizFactoryServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.biz; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizOperation; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.biz.BizFactoryService; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.plugin.PluginFactoryService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import org.junit.Assert; import org.junit.Test; import java.io.IOException; import java.net.URL; import static com.alipay.sofa.ark.api.ArkConfigs.putStringValue; import static com.alipay.sofa.ark.container.service.ArkServiceContainerHolder.getContainer; import static com.alipay.sofa.ark.spi.constant.Constants.*; import static com.alipay.sofa.ark.spi.constant.Constants.UNPACK_BIZ_WHEN_INSTALL; import static java.lang.Thread.currentThread; import static org.junit.Assert.assertNotNull; /** * @author qilong.zql * @since 0.4.0 */ public class BizFactoryServiceTest extends BaseTest { private PluginFactoryService pluginFactoryService; private PluginManagerService pluginManagerService; private BizFactoryService bizFactoryService; private BizManagerService bizManagerService; @Override public void before() { super.before(); pluginManagerService = arkServiceContainer.getService(PluginManagerService.class); pluginFactoryService = arkServiceContainer.getService(PluginFactoryService.class); bizFactoryService = arkServiceContainer.getService(BizFactoryService.class); bizManagerService = getContainer().getService(BizManagerService.class); } @Test public void test() throws Throwable { ClassLoader cl = currentThread().getContextClassLoader(); URL samplePlugin = cl.getResource("sample-plugin.jar"); Plugin plugin = pluginFactoryService.createPlugin(FileUtils.file(samplePlugin.getFile())); pluginManagerService.registerPlugin(plugin); URL sampleBiz = cl.getResource("sample-biz.jar"); Biz biz = bizFactoryService.createBiz(FileUtils.file(sampleBiz.getFile())); bizManagerService.registerBiz(biz); assertNotNull(biz); assertNotNull(biz.getBizClassLoader().getResource(ARK_PLUGIN_MARK_ENTRY)); putStringValue(MASTER_BIZ, "master-biz"); Biz masterBiz = bizFactoryService.createEmbedMasterBiz(cl); assertNotNull(masterBiz); assertNotNull(masterBiz.getBizClassLoader().getResource( "com/alipay/sofa/ark/container/service/biz/")); } @Test public void testCreateBizWithoutBizOperation() throws IOException { String originalEmbed = System.getProperty(EMBED_ENABLE); String originalUnpack = System.getProperty(UNPACK_BIZ_WHEN_INSTALL); try { ClassLoader cl = currentThread().getContextClassLoader(); URL sampleBiz = cl.getResource("sample-biz.jar"); System.setProperty(EMBED_ENABLE, "true"); System.setProperty(UNPACK_BIZ_WHEN_INSTALL, "false"); Biz biz = bizFactoryService.createBiz(FileUtils.file(sampleBiz.getFile())); assertNotNull(biz); } finally { if (originalEmbed != null) { System.setProperty(EMBED_ENABLE, originalEmbed); } else { System.clearProperty(EMBED_ENABLE); } if (originalUnpack != null) { System.setProperty(UNPACK_BIZ_WHEN_INSTALL, originalUnpack); } else { System.clearProperty(UNPACK_BIZ_WHEN_INSTALL); } } } @Test public void testCreateBiz() throws IOException { ClassLoader cl = currentThread().getContextClassLoader(); URL sampleBiz = cl.getResource("sample-biz.jar"); BizOperation bizOperation = new BizOperation(); String mockVersion = "mock version"; bizOperation.setBizVersion(mockVersion); Biz biz = bizFactoryService.createBiz(bizOperation, FileUtils.file(sampleBiz.getFile())); Assert.assertEquals(biz.getBizVersion(), mockVersion); } @Test public void testPackageInfo() throws Throwable { ClassLoader cl = currentThread().getContextClassLoader(); URL samplePlugin = cl.getResource("sample-ark-plugin-common-0.5.1.jar"); Plugin plugin = pluginFactoryService.createPlugin(FileUtils.file(samplePlugin.getFile())); ClassLoader pluginClassLoader = plugin.getPluginClassLoader(); pluginManagerService.registerPlugin(plugin); Class mdc = pluginClassLoader.loadClass("org.slf4j.MDC"); Assert.assertTrue(mdc.getClassLoader().equals(pluginClassLoader)); assertNotNull(mdc.getPackage().getImplementationVersion()); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.biz; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import org.junit.Before; import org.junit.Test; import java.util.List; import java.util.Set; import static com.alipay.sofa.ark.spi.model.BizState.ACTIVATED; import static com.alipay.sofa.ark.spi.model.BizState.RESOLVED; import static org.codehaus.plexus.util.ReflectionUtils.setVariableValueInObject; import static org.junit.Assert.*; /** * @author qilong.zql * @since 0.4.0 */ public class BizManagerServiceTest extends BaseTest { private BizManagerService bizManagerService = new BizManagerServiceImpl(); @Before public void before() { super.before(); Biz biz = new BizModel().setBizName("test-biz").setBizVersion("1.0.0") .setBizState(RESOLVED); bizManagerService.registerBiz(biz); } @Test public void testRegisterBiz() { Biz ret = bizManagerService.getBiz("test-biz", "1.0.0"); assertNotNull(ret); } @Test public void testDuplicatedRegisterBiz() { Biz biz = new BizModel().setBizName("test-biz").setBizVersion("1.0.0") .setBizState(RESOLVED); assertFalse(bizManagerService.registerBiz(biz)); assertEquals(1, bizManagerService.getBiz("test-biz").size()); } @Test public void testRemovingAndAddBiz() { Biz adding = new BizModel().setBizName("test-biz-adding").setBizVersion("1.0.0") .setBizState(ACTIVATED); Biz removing = new BizModel().setBizName("test-biz-removing").setBizVersion("1.0.0") .setBizState(RESOLVED); bizManagerService.registerBiz(removing); ((BizModel) removing).setBizState(ACTIVATED); bizManagerService.removeAndAddBiz(adding, removing); List biz = bizManagerService.getBiz("test-biz-adding"); assertTrue(biz.size() == 1); biz = bizManagerService.getBiz("test-biz-removing"); assertTrue(biz.size() == 0); bizManagerService.unRegisterBiz(adding.getBizName(), adding.getBizVersion()); } @Test public void testUnRegister() { Biz biz = bizManagerService.unRegisterBiz("test-biz", "1.0.1"); assertNull(biz); assertTrue(bizManagerService.getBiz("test-biz").size() == 1); biz = bizManagerService.unRegisterBizStrictly("test-biz", "1.0.0"); assertNotNull(biz); assertTrue(bizManagerService.getBiz("test-biz").size() == 0); bizManagerService.registerBiz(biz); assertTrue(bizManagerService.getBiz("test-biz").size() == 1); } @Test public void testBizGet() { Biz biz = bizManagerService.getBizByIdentity("test-biz:1.0.0"); assertNotNull(biz); Set stringSet = bizManagerService.getAllBizNames(); assertTrue(stringSet.size() == 1); assertTrue(stringSet.contains("test-biz")); biz = bizManagerService.getActiveBiz("test-biz"); assertNull(biz); BizState bizState = bizManagerService.getBizState("test-biz:1.0.0"); assertTrue(bizState == RESOLVED); bizState = bizManagerService.getBizState("test-biz", "1.0.0"); assertTrue(bizState == RESOLVED); bizState = bizManagerService.getBizState("ss", "xx"); assertTrue(bizState == BizState.UNRESOLVED); biz = new BizModel().setBizName("test-biz").setBizVersion("1.0.1").setBizState(RESOLVED) .setPriority("10"); bizManagerService.registerBiz(biz); List bizList = bizManagerService.getBizInOrder(); assertTrue(bizList.size() == 2); assertTrue(bizList.get(0).getBizVersion().equals("1.0.1")); assertTrue(bizList.get(1).getBizVersion().equals("1.0.0")); biz = bizManagerService.getActiveBiz("test-biz"); assertNull(biz); bizManagerService.activeBiz("test-biz", "1.0.1"); assertTrue(bizManagerService.getBizState("test-biz", "1.0.1") == RESOLVED); assertTrue(bizManagerService.getBizState("test-biz", "1.0.0") == RESOLVED); biz = bizManagerService.getBiz("test-biz", "1.0.1"); ((BizModel) biz).setBizState(BizState.DEACTIVATED); bizManagerService.activeBiz("test-biz", "1.0.1"); assertTrue(bizManagerService.getBizState("test-biz", "1.0.1") == ACTIVATED); assertTrue(bizManagerService.getBizState("test-biz", "1.0.0") == RESOLVED); bizManagerService.activeBiz("test-biz", "1.0.0"); assertTrue(bizManagerService.getBizState("test-biz", "1.0.1") == ACTIVATED); assertTrue(bizManagerService.getBizState("test-biz", "1.0.0") == RESOLVED); biz = bizManagerService.getBiz("test-biz", "1.0.0"); ((BizModel) biz).setBizState(BizState.DEACTIVATED); bizManagerService.activeBiz("test-biz", "1.0.0"); assertTrue(bizManagerService.getBizState("test-biz", "1.0.0") == ACTIVATED); assertTrue(bizManagerService.getBizState("test-biz", "1.0.1") == BizState.DEACTIVATED); } @Test(expected = ArkRuntimeException.class) public void testDeployWithException() throws IllegalAccessException { Biz biz = new BizModel().setBizName("test-biz").setBizVersion("1.0.3") .setBizState(RESOLVED).setPriority("10"); bizManagerService.registerBiz(biz); DefaultBizDeployer defaultBizDeployer = new DefaultBizDeployer(); setVariableValueInObject(defaultBizDeployer, "bizManagerService", bizManagerService); defaultBizDeployer.deploy(); } @Test(expected = ArkRuntimeException.class) public void testUndeployWithException() throws IllegalAccessException { Biz biz = new BizModel().setBizName("test-biz").setBizVersion("1.0.3") .setBizState(RESOLVED).setPriority("10"); bizManagerService.registerBiz(biz); DefaultBizDeployer defaultBizDeployer = new DefaultBizDeployer(); setVariableValueInObject(defaultBizDeployer, "bizManagerService", bizManagerService); defaultBizDeployer.unDeploy(); } @Test public void testIsActiveBiz() { bizManagerService = new BizManagerServiceImpl(); assertNull(bizManagerService.getBizByClassLoader(this.getClass().getClassLoader())); Biz biz = new BizModel().setBizName("test-biz").setBizVersion("1.0.1") .setBizState(RESOLVED).setPriority("10"); bizManagerService.registerBiz(biz); assertFalse(bizManagerService.isActiveBiz("test-biz", "1.0.1")); assertFalse(bizManagerService.isActiveBiz("test-biz", "2.0.1")); assertNotNull(bizManagerService.getBizRegistration()); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/biz/hook/TestAddBizToStaticDeployHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.biz.hook; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.loader.JarBizArchive; import com.alipay.sofa.ark.loader.archive.JarFileArchive; import com.alipay.sofa.ark.spi.archive.BizArchive; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.service.biz.AddBizToStaticDeployHook; import com.alipay.sofa.ark.spi.service.extension.Extension; import com.google.common.collect.Lists; import java.io.File; import java.util.ArrayList; import java.util.List; /** * @author lianglipeng.llp@alibaba-inc.com * @version $Id: TestBeforeEmbedStaticDeployBizHook.java, v 0.1 2024年06月27日 16:36 立蓬 Exp $ */ @Extension("before-embed-static-deploy-biz-hook") public class TestAddBizToStaticDeployHook implements AddBizToStaticDeployHook { @Override public List getStaticBizToAdd() throws Exception { List archives = new ArrayList<>(); File bizFile = new File(this.getClass().getClassLoader() .getResource("sample-ark-1.0.0-ark-biz.jar").toURI()); archives.add(new JarBizArchive(new JarFileArchive(bizFile))); return archives; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/classloader/BizClassLoaderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.bootstrap.AgentClassLoader; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.common.util.ClassUtils; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.model.PluginModel; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.container.testdata.ITest; import com.alipay.sofa.ark.exception.ArkLoaderException; import com.alipay.sofa.ark.spi.model.*; import com.alipay.sofa.ark.spi.service.biz.BizFactoryService; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderService; import com.alipay.sofa.ark.spi.service.plugin.PluginDeployService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import com.google.common.cache.Cache; import com.google.common.collect.Sets; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.lang.reflect.Field; import java.net.URL; import java.util.*; import static com.google.common.collect.Lists.newArrayList; import static java.lang.Thread.currentThread; /** * @author ruoshan * @since 0.1.0 */ public class BizClassLoaderTest extends BaseTest { private URL classPathURL = PluginClassLoaderTest.class.getClassLoader() .getResource(""); private PluginManagerService pluginManagerService; private PluginDeployService pluginDeployService; private ClassLoaderService classloaderService; private BizManagerService bizManagerService; @Before public void before() { super.before(); pluginManagerService = ArkServiceContainerHolder.getContainer().getService( PluginManagerService.class); pluginDeployService = ArkServiceContainerHolder.getContainer().getService( PluginDeployService.class); classloaderService = ArkServiceContainerHolder.getContainer().getService( ClassLoaderService.class); bizManagerService = ArkServiceContainerHolder.getContainer().getService( BizManagerService.class); } @Test public void testImport() throws Exception { PluginModel pluginA = new PluginModel(); pluginA .setPluginName("plugin A") .setClassPath(new URL[] { classPathURL }) .setImportClasses(StringUtils.EMPTY_STRING) .setImportPackages(StringUtils.EMPTY_STRING) .setExportClasses("") .setExportPackages(ClassUtils.getPackageName(ITest.class.getName())) .setImportResources(StringUtils.EMPTY_STRING) .setExportResources(StringUtils.EMPTY_STRING) .setPluginClassLoader( new PluginClassLoader(pluginA.getPluginName(), pluginA.getClassPath())); pluginManagerService.registerPlugin(pluginA); pluginDeployService.deploy(); classloaderService.prepareExportClassAndResourceCache(); BizModel bizModel = createTestBizModel("biz A", "1.0.0", BizState.RESOLVED, new URL[] { classPathURL }); bizModel.setDenyImportResources(StringUtils.EMPTY_STRING); bizModel.setDenyImportClasses(StringUtils.EMPTY_STRING); bizModel.setDenyImportPackages(StringUtils.EMPTY_STRING); bizModel.getExportNodeAndClassLoaderMap().put( ClassUtils.getPackageName(ITest.class.getName()), pluginA); bizManagerService.registerBiz(bizModel); Assert.assertEquals(pluginA.getPluginClassLoader().loadClass(ITest.class.getName()), bizModel.getBizClassLoader().loadClass(ITest.class.getName())); } @Test public void testAgentClass() throws ClassNotFoundException { BizModel bizModel = createTestBizModel("biz A", "1.0.0", BizState.RESOLVED, new URL[] {}); bizModel.setDenyImportResources("").setDenyImportClasses(""); bizManagerService.registerBiz(bizModel); Class clazz = bizModel.getBizClassLoader().loadClass("SampleClass"); Assert.assertFalse(clazz.getClassLoader() instanceof AgentClassLoader); Assert.assertTrue(clazz.getClassLoader().getClass().getCanonicalName() .contains("Launcher.AppClassLoader") || clazz.getClassLoader().getClass().getCanonicalName() .contains("ClassLoaders.AppClassLoader")); } @Test public void testLoadClassFromPluginClassLoader() throws Exception { URL bizUrl = this.getClass().getClassLoader().getResource("sample-ark-1.0.0-ark-biz.jar"); URL pluginUrl1 = this.getClass().getClassLoader().getResource("sample-ark-plugin-common-0.5.1.jar"); URL pluginUrl2 = this.getClass().getClassLoader().getResource("sofa-ark-sample-springboot-ark-0.3.0.jar"); URL pluginUrl3 = this.getClass().getClassLoader().getResource("aopalliance-1.0.jar"); URL pluginUrl4 = this.getClass().getClassLoader().getResource("com.springsource.org.aopalliance-1.0.0.jar"); BizModel bizModel = createTestBizModel("biz A", "1.0.0", BizState.RESOLVED, new URL[] { bizUrl }); bizModel.setDenyImportClasses(StringUtils.EMPTY_STRING); bizModel.setDenyImportPackages(StringUtils.EMPTY_STRING); bizModel.setDenyImportResources(StringUtils.EMPTY_STRING); bizModel.setDeclaredLibraries("sample-ark-plugin-common,com.springsource.org.aopalliance"); PluginModel pluginA = new PluginModel(); pluginA .setPluginName("plugin A") .setClassPath(new URL[] { classPathURL }) .setImportClasses(StringUtils.EMPTY_STRING) .setImportPackages(StringUtils.EMPTY_STRING) .setExportClasses("") .setExportPackages(ClassUtils.getPackageName(ITest.class.getName())) .setImportResources(StringUtils.EMPTY_STRING) .setExportResources("META-INF/services/sofa-ark/com.alipay.sofa.ark.container.service.extension.spi.ServiceB, Sample_Resource_Exported_A") .setPluginClassLoader( new PluginClassLoader(pluginA.getPluginName(), pluginA.getClassPath())); PluginModel pluginB = new PluginModel(); pluginB .setPluginName("plugin B") .setClassPath(new URL[] { pluginUrl1, pluginUrl2, pluginUrl3, pluginUrl4 }) .setImportClasses(StringUtils.EMPTY_STRING) .setImportPackages(StringUtils.EMPTY_STRING) .setExportClasses("com.alipay.sofa.ark.sample.common.SampleClassExported,org.aopalliance.aop.Advice") .setExportPackages("") .setImportResources(StringUtils.EMPTY_STRING) .setExportResources("Sample_Resource_Exported, META-INF/spring/service.xml, Sample_Resource_Exported_A") .setPluginClassLoader( new PluginClassLoader(pluginB.getPluginName(), pluginB.getClassPath())); pluginManagerService.registerPlugin(pluginA); pluginManagerService.registerPlugin(pluginB); pluginDeployService.deploy(); classloaderService.prepareExportClassAndResourceCache(); bizModel.getExportNodeAndClassLoaderMap().put(ClassUtils.getPackageName(ITest.class.getName()), pluginA); bizModel.getExportClassAndClassLoaderMap().put("org.aopalliance.aop.Advice", pluginB); bizModel.getExportClassAndClassLoaderMap().put("com.alipay.sofa.ark.sample.common.SampleClassExported", pluginB); bizModel.getExportResourceAndClassLoaderMap().put("META-INF/spring/service.xml", newArrayList(pluginB)); bizModel.getExportResourceAndClassLoaderMap().put("Sample_Resource_Exported", newArrayList(pluginB)); bizModel.getExportResourceAndClassLoaderMap().put("META-INF/services/sofa-ark/com.alipay.sofa.ark.container.service.extension.spi.ServiceB", newArrayList(pluginA)); bizManagerService.registerBiz(bizModel); // case 1: find class from multiple libs in plugin classloader Class adviceClazz = bizModel.getBizClassLoader().loadClass("org.aopalliance.aop.Advice"); Assert.assertEquals(adviceClazz.getClassLoader(), pluginB.getPluginClassLoader()); // case 2: find class from plugin but not set provided in biz model Assert.assertThrows(ArkLoaderException.class, () -> bizModel.getBizClassLoader().loadClass("com.alipay.sofa.ark.sample.facade.SampleService")); // case 3: find class from plugin in classpath Class itest = bizModel.getBizClassLoader().loadClass(ITest.class.getName()); Assert.assertEquals(itest.getClassLoader(), pluginA.getPluginClassLoader()); // case 4: find class from plugin in jar Class sampleClassExported = bizModel.getBizClassLoader().loadClass("com.alipay.sofa.ark.sample.common.SampleClassExported"); Assert.assertEquals(sampleClassExported.getClassLoader(), pluginB.getPluginClassLoader()); // case 5: find class but not exported Assert.assertThrows(ArkLoaderException.class, () -> bizModel.getBizClassLoader().loadClass("com.alipay.sofa.ark.sample.common.SampleClassNotExported")); // case 6: find resource from plugin but not set provided in biz model Assert.assertNull(bizModel.getBizClassLoader().getResource("Sample_Resource_Not_Exported")); // case 7: find sofa-ark resources from plugin in biz model Assert.assertNotNull(bizModel.getBizClassLoader().getResource("META-INF/spring/service.xml")); // case 8: find resource from plugin in classpath Assert.assertNotNull(bizModel.getBizClassLoader().getResource("META-INF/services/sofa-ark/com.alipay.sofa.ark.container.service.extension.spi.ServiceB")); // case 9: find resource from plugin in jar Assert.assertNotNull(bizModel.getBizClassLoader().getResource("Sample_Resource_Exported")); // case 10: find resource but not exproted Assert.assertNull(bizModel.getBizClassLoader().getResource("Sample_Resource_Not_Exported")); // case 10: find resources from plugin but not set provided in biz model Assert.assertFalse(bizModel.getBizClassLoader().getResources("Sample_Resource_Not_Exported").hasMoreElements()); // case 11: find resource from plugin in classpath Assert.assertTrue(bizModel.getBizClassLoader().getResources("META-INF/services/sofa-ark/com.alipay.sofa.ark.container.service.extension.spi.ServiceB").hasMoreElements()); // case 12: find resource from plugin in jar Assert.assertTrue(bizModel.getBizClassLoader().getResources("Sample_Resource_Exported").hasMoreElements()); // case 13: find resource but not exproted Assert.assertFalse(bizModel.getBizClassLoader().getResources("Sample_Resource_Not_Exported").hasMoreElements()); } @Test public void testLoadOverrideClassFromPluginClassLoader() throws Exception { URL bizUrl = this.getClass().getClassLoader().getResource("sample-ark-1.0.0-ark-biz.jar"); URL pluginUrl1 = this.getClass().getClassLoader() .getResource("sample-ark-plugin-common-0.5.1.jar"); URL pluginUrl2 = this.getClass().getClassLoader() .getResource("sofa-ark-sample-springboot-ark-0.3.0.jar"); BizModel bizModel = createTestBizModel("biz A", "1.0.0", BizState.RESOLVED, new URL[] { bizUrl }); bizModel.setDenyImportClasses(StringUtils.EMPTY_STRING); bizModel.setDenyImportPackages(StringUtils.EMPTY_STRING); bizModel.setDenyImportResources(StringUtils.EMPTY_STRING); bizModel.setDeclaredLibraries("sample-ark-plugin-common, sofa-ark-sample-springboot-ark"); PluginModel pluginA = new PluginModel(); pluginA .setPluginName("plugin A") .setClassPath(new URL[] { classPathURL, pluginUrl1, pluginUrl2 }) .setImportClasses(StringUtils.EMPTY_STRING) .setImportPackages(StringUtils.EMPTY_STRING) .setExportMode(PluginModel.EXPORTMODE_OVERRIDE) .setExportClasses("com.alipay.sofa.ark.sample.common.SampleClassExported") .setExportPackages("") .setImportResources(StringUtils.EMPTY_STRING) .setExportResources("") .setPluginClassLoader( new PluginClassLoader(pluginA.getPluginName(), pluginA.getClassPath())); PluginModel pluginB = new PluginModel(); pluginB .setPluginName("plugin B") .setClassPath(new URL[] { classPathURL, pluginUrl1, pluginUrl2 }) .setImportClasses(StringUtils.EMPTY_STRING) .setImportPackages(StringUtils.EMPTY_STRING) .setExportMode(PluginModel.EXPORTMODE_CLASSLOADER) .setExportClasses("com.alipay.sofa.ark.sample.springbootdemo.SpringbootDemoApplication") .setExportPackages("") .setImportResources(StringUtils.EMPTY_STRING) .setExportResources("") .setPluginClassLoader( new PluginClassLoader(pluginB.getPluginName(), pluginB.getClassPath())); bizModel.getExportClassAndClassLoaderMap().put( "com.alipay.sofa.ark.sample.springbootdemo.SpringbootDemoApplication", pluginB); pluginManagerService.registerPlugin(pluginA); pluginManagerService.registerPlugin(pluginB); pluginDeployService.deploy(); classloaderService.prepareExportClassAndResourceCache(); bizManagerService.registerBiz(bizModel); // case 1: find class from multiple libs in plugin classloader Class adviceClazz1 = bizModel.getBizClassLoader().loadClass( "com.alipay.sofa.ark.sample.common.SampleClassExported"); Assert.assertEquals(adviceClazz1.getClassLoader(), bizModel.getBizClassLoader()); Class adviceClazz2 = bizModel.getBizClassLoader().loadClass( "com.alipay.sofa.ark.sample.springbootdemo.SpringbootDemoApplication"); Assert.assertEquals(adviceClazz2.getClassLoader(), pluginB.getPluginClassLoader()); } @Test public void testGetPluginClassResource() { PluginModel pluginA = new PluginModel(); pluginA .setPluginName("plugin A") .setClassPath(new URL[] { classPathURL }) .setImportClasses(StringUtils.EMPTY_STRING) .setImportPackages(StringUtils.EMPTY_STRING) .setExportPackages("") .setExportClasses(ITest.class.getName()) .setImportResources(StringUtils.EMPTY_STRING) .setExportResources(StringUtils.EMPTY_STRING) .setPluginClassLoader( new PluginClassLoader(pluginA.getPluginName(), pluginA.getClassPath())); pluginManagerService.registerPlugin(pluginA); pluginDeployService.deploy(); classloaderService.prepareExportClassAndResourceCache(); BizModel bizModel = createTestBizModel("biz A", "1.0.0", BizState.RESOLVED, new URL[0]); bizModel.setDenyImportClasses(StringUtils.EMPTY_STRING) .setDenyImportResources(StringUtils.EMPTY_STRING) .setDenyImportPackages(StringUtils.EMPTY_STRING); bizManagerService.registerBiz(bizModel); Assert.assertNotNull(bizModel.getBizClassLoader().getResource( ITest.class.getName().replace(".", "/") + ".class")); } @Test public void testLoadClassFromAgentClassLoader() throws ClassNotFoundException { BizModel bizModel = createTestBizModel("MockBiz", "1.0.0", BizState.RESOLVED, new URL[] {}); bizModel.setDenyImportResources(StringUtils.EMPTY_STRING); bizModel.setDenyImportClasses(StringUtils.EMPTY_STRING); bizModel.setDenyImportPackages(StringUtils.EMPTY_STRING); bizManagerService.registerBiz(bizModel); BizClassLoader bizClassLoader = (BizClassLoader) bizModel.getBizClassLoader(); Assert.assertNotNull(bizClassLoader.loadClass("SampleClass", false)); Class clazz = bizClassLoader.loadClass(ArkClient.class.getCanonicalName()); Assert.assertTrue(clazz.getClassLoader().equals(classloaderService.getArkClassLoader())); } @Test public void testDenyImport() throws Exception { PluginModel pluginA = new PluginModel(); pluginA .setPluginName("pluginA") .setClassPath(new URL[] { classPathURL }) .setImportClasses(StringUtils.EMPTY_STRING) .setImportPackages(StringUtils.EMPTY_STRING) .setImportResources(StringUtils.EMPTY_STRING) .setExportResources("pluginA_export_resource1.xml,pluginA_export_resource2.xml") .setExportClasses(ITest.class.getName()) .setPluginClassLoader( new PluginClassLoader(pluginA.getPluginName(), pluginA.getClassPath())); pluginManagerService.registerPlugin(pluginA); pluginDeployService.deploy(); classloaderService.prepareExportClassAndResourceCache(); BizModel bizModel = createTestBizModel("bizA", "1.0.0", BizState.RESOLVED, new URL[] {}); Set plugins = new HashSet<>(); plugins.add(pluginA); bizModel.setDependentPlugins(plugins); bizModel.getExportResourceAndClassLoaderMap().put("pluginA_export_resource1.xml", newArrayList(pluginA)); bizModel.getExportResourceAndClassLoaderMap().put("pluginA_export_resource2.xml", newArrayList(pluginA)); bizModel.setDenyImportResources(StringUtils.EMPTY_STRING); bizModel.setDenyImportPackages(StringUtils.EMPTY_STRING); bizModel.setDenyImportClasses(StringUtils.EMPTY_STRING); bizManagerService.registerBiz(bizModel); Assert.assertNotNull(bizModel.getBizClassLoader().getResource( "pluginA_export_resource1.xml")); Assert.assertNotNull(bizModel.getBizClassLoader().getResource( "pluginA_export_resource2.xml")); bizModel.setDenyImportResources("pluginA_export_resource2.xml"); invalidClassLoaderCache(bizModel.getBizClassLoader()); Assert.assertNull(bizModel.getBizClassLoader().getResource("pluginA_export_resource2.xml")); Assert.assertFalse(bizModel.getBizClassLoader().loadClass(ITest.class.getName()) .getClassLoader() instanceof PluginClassLoader); bizModel.setDenyImportPackages("com.alipay.sofa.ark.container.testdata"); invalidClassLoaderCache(bizModel.getBizClassLoader()); Assert.assertFalse(bizModel.getBizClassLoader().loadClass(ITest.class.getName()) .getClassLoader() instanceof PluginClassLoader); bizModel.setDenyImportPackages(StringUtils.EMPTY_STRING); bizModel.setDenyImportClasses(ITest.class.getCanonicalName()); invalidClassLoaderCache(bizModel.getBizClassLoader()); Assert.assertFalse(bizModel.getBizClassLoader().loadClass(ITest.class.getName()) .getClassLoader() instanceof PluginClassLoader); } @Test public void testDenyImportResourceStems() { PluginModel pluginA = new PluginModel(); pluginA .setPluginName("pluginA") .setClassPath(new URL[] { classPathURL }) .setImportClasses(StringUtils.EMPTY_STRING) .setImportPackages(StringUtils.EMPTY_STRING) .setImportResources(StringUtils.EMPTY_STRING) .setExportResources("export/folderA/*,export/folderB/*") .setExportClasses(ITest.class.getName()) .setPluginClassLoader( new PluginClassLoader(pluginA.getPluginName(), pluginA.getClassPath())); pluginManagerService.registerPlugin(pluginA); pluginDeployService.deploy(); classloaderService.prepareExportClassAndResourceCache(); BizModel bizModel = createTestBizModel("bizA", "1.0.0", BizState.RESOLVED, new URL[0]); bizModel.setDenyImportResources("export/folderA/*,export/folderB/test3.xml"); bizModel.setDenyImportPackages(StringUtils.EMPTY_STRING); bizModel.setDenyImportClasses(StringUtils.EMPTY_STRING); bizManagerService.registerBiz(bizModel); String testResource1 = "export/folderA/test1.xml"; String testResource2 = "export/folderA/test2.xml"; String testResource3 = "export/folderB/test3.xml"; String testResource4 = "export/folderB/test4.xml"; Assert.assertNull(bizModel.getBizClassLoader().getResource(testResource1)); Assert.assertNull(bizModel.getBizClassLoader().getResource(testResource2)); Assert.assertNull(bizModel.getBizClassLoader().getResource(testResource3)); Assert.assertNull(bizModel.getBizClassLoader().getResource(testResource4)); } @Test public void testSlashResource() throws Throwable { registerMockBiz(); // URLClassLoader urlClassLoader = (URLClassLoader) this.getClass().getClassLoader(); // Field ucpFiled = URLClassLoader.class.getDeclaredField("ucp"); // ucpFiled.setAccessible(true); // URLClassPath ucp = (URLClassPath) ucpFiled.get(urlClassLoader); // BizClassLoader bizClassLoader = new BizClassLoader("mock:1.0", ucp.getURLs()); ClassLoader classLoader = this.getClass().getClassLoader(); BizClassLoader bizClassLoader = new BizClassLoader("mock:1.0", ClassLoaderUtils.getURLs(classLoader)); BizManagerService bizManagerService = ArkServiceContainerHolder.getContainer().getService( BizManagerService.class); bizClassLoader.setBizModel((BizModel) bizManagerService.getBiz("mock", "1.0")); URL url = bizClassLoader.getResource(""); Assert.assertNotNull(url); Assert.assertEquals(url, this.getClass().getResource("/")); } @Test public void testGetJdkResource() throws IOException { BizModel bizModel = createTestBizModel("biz A", "1.0.0", BizState.RESOLVED, new URL[] {}); bizManagerService.registerBiz(bizModel); ClassLoader cl = bizModel.getBizClassLoader(); // String name = "META-INF/services/javax.script.ScriptEngineFactory"; String name = "javax/lang/model/element/Modifier.class"; URL res1 = cl.getResource(name); Assert.assertNotNull(res1); URL res2 = ClassLoader.getSystemClassLoader().getResource(name); Assert.assertEquals(res2, res1); Enumeration enu1 = cl.getResources(name); Assert.assertTrue(enu1.hasMoreElements()); Enumeration enu2 = ClassLoader.getSystemClassLoader().getResources(name); Assert.assertEquals(Sets.newHashSet(Collections.list(enu2)), Sets.newHashSet(Collections.list(enu1))); } @Test public void testCacheResource() throws NoSuchFieldException, IllegalAccessException { BizModel bizModel = createTestBizModel("biz A", "1.0.0", BizState.RESOLVED, new URL[] {}); bizManagerService.registerBiz(bizModel); ClassLoader cl = bizModel.getBizClassLoader(); // String name = "META-INF/services/javax.script.ScriptEngineFactory"; String name = "javax/lang/model/element/Modifier.class"; URL res1 = cl.getResource(name); Assert.assertNotNull(res1); Assert.assertNotNull(cl.getResource(name)); Cache> urlResourceCache = getUrlResourceCache(cl); Assert.assertNotNull(urlResourceCache.getIfPresent(name)); Assert.assertNotNull(urlResourceCache.getIfPresent(name).get()); // not existing url String notExistingName = "META-INF/services/javax.script.ScriptEngineFactory/NotExisting"; URL notExistingRes = cl.getResource(notExistingName); Assert.assertNull(notExistingRes); Assert.assertNull(cl.getResource(notExistingName)); Assert.assertNotNull(urlResourceCache.getIfPresent(notExistingName)); Assert.assertFalse(urlResourceCache.getIfPresent(notExistingName).isPresent()); } @Test public void testPublicDefineClass() { BizModel bizModel = createTestBizModel("biz A", "1.0.0", BizState.RESOLVED, new URL[] {}); bizManagerService.registerBiz(bizModel); BizClassLoader cl = (BizClassLoader) bizModel.getBizClassLoader(); try { cl.publicDefineClass("NoExistClass", new byte[] {}, null); Assert.fail(); } catch (Throwable t) { Assert.assertTrue(t instanceof java.lang.ClassFormatError); } } private Cache> getUrlResourceCache(Object classloader) throws NoSuchFieldException, IllegalAccessException { Field field = AbstractClasspathClassLoader.class.getDeclaredField("urlResourceCache"); field.setAccessible(true); return (Cache>) field.get(classloader); } private void invalidClassLoaderCache(ClassLoader classloader) { if (classloader instanceof AbstractClasspathClassLoader) { ((AbstractClasspathClassLoader) classloader).invalidAllCache(); } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/classloader/ClassLoaderConcurrencyTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader; import com.alipay.sofa.ark.common.thread.CommonThreadPool; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.testdata.classloader.ClassLoaderTestClass; import org.junit.Assert; import org.junit.Test; import java.net.URL; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicBoolean; /** * Test class load concurrency * @author ruoshan * @since 0.5.0 */ public class ClassLoaderConcurrencyTest extends BaseTest { private URL classPathURL = ClassLoaderConcurrencyTest.class.getClassLoader().getResource(""); @Test public void concurrencyLoadClass() { final BizClassLoader bizClassLoader = new BizClassLoader("test:1.0", new URL[] { classPathURL }); final AtomicBoolean result = new AtomicBoolean(true); int totalTimes = 100; ThreadPoolExecutor executor = new CommonThreadPool().getExecutor(); final CountDownLatch countDownLatch = new CountDownLatch(totalTimes); for (int index = 0; index < totalTimes; index++) { if (result.get()) { executor.execute(new Runnable() { @Override public void run() { try { bizClassLoader.loadClass(ClassLoaderTestClass.class.getName(), true); } catch (ClassNotFoundException e) { // ingore } catch (LinkageError e) { result.set(false); } finally { countDownLatch.countDown(); } } }); } else { countDownLatch.countDown(); } } try { countDownLatch.await(); } catch (InterruptedException e) { // ignore } finally { executor.shutdown(); } Assert.assertTrue("should not get linkega error when load class", result.get()); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/classloader/ClassLoaderHookTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.container.service.classloader.hook.TestBizClassLoaderHook; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.service.classloader.hook.TestDefaultBizClassLoaderHook; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderHook; import com.alipay.sofa.ark.spi.service.extension.ArkServiceLoader; import org.junit.Assert; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import java.net.URL; import java.util.Enumeration; import static com.alipay.sofa.ark.spi.constant.Constants.*; /** * @author qilong.zql * @since 0.6.0 */ public class ClassLoaderHookTest extends BaseTest { @Override public void before() { super.before(); registerMockBiz(); registerMockPlugin(); } @Test public void testBizClassLoaderSPI() throws Throwable { BizClassLoader bizClassLoader = new BizClassLoader("mock:1.0", (ClassLoaderUtils.getURLs(this.getClass().getClassLoader()))); Assert.assertTrue(TestBizClassLoaderHook.ClassA.class.getName().equals( bizClassLoader.loadClass("A.A").getName())); BizManagerService bizManagerService = ArkServiceContainerHolder.getContainer().getService( BizManagerService.class); bizClassLoader.setBizModel((BizModel) bizManagerService.getBiz("mock", "1.0")); Assert.assertTrue(TestBizClassLoaderHook.ClassB.class.getName().equals( bizClassLoader.loadClass("B").getName())); Assert.assertTrue(bizClassLoader.getResource("R1").getFile() .endsWith("pluginA_export_resource1.xml")); Assert.assertTrue(bizClassLoader.getResource("sample-biz.jar").getFile() .endsWith("sample-biz.jar")); Assert.assertTrue(bizClassLoader.getResource("any").getFile() .endsWith("pluginA_export_resource2.xml")); Enumeration urls = bizClassLoader.getResources("R2"); Assert.assertTrue(urls.hasMoreElements()); URL url = urls.nextElement(); Assert.assertFalse(urls.hasMoreElements()); Assert.assertTrue(url.getFile().contains("sample-biz.jar")); urls = bizClassLoader.getResources("test.jar"); Assert.assertTrue(urls.hasMoreElements()); url = urls.nextElement(); Assert.assertFalse(urls.hasMoreElements()); Assert.assertTrue(url.getFile().contains("test.jar")); urls = bizClassLoader.getResources("any"); Assert.assertTrue(urls.hasMoreElements()); url = urls.nextElement(); Assert.assertFalse(urls.hasMoreElements()); Assert.assertTrue(url.getFile().contains("sample-plugin.jar")); } @Test public void testDefaultBizClassLoaderSPI() throws Throwable { try (MockedStatic arkServiceLoaderMocked = Mockito.mockStatic(ArkServiceLoader.class); MockedStatic arkClientMocked = Mockito.mockStatic(ArkClient.class)) { arkServiceLoaderMocked.when(() -> ArkServiceLoader.loadExtensionFromArkBiz(ClassLoaderHook.class, BIZ_CLASS_LOADER_HOOK, "mock_default_classloader:1.0")).thenReturn(null); arkClientMocked.when(ArkClient::getMasterBiz).thenReturn(new BizModel().setBizName("mock_master_biz").setBizVersion("1.0") .setClassLoader(this.getClass().getClassLoader())); BizClassLoader bizClassLoader = new BizClassLoader("mock_default_classloader:1.0", (ClassLoaderUtils.getURLs(this.getClass().getClassLoader()))); System.setProperty(BIZ_CLASS_LOADER_HOOK_DIR, "com.alipay.sofa.ark.container.service.classloader.hook.TestDefaultBizClassLoaderHook"); Assert.assertTrue(TestDefaultBizClassLoaderHook.ClassDefaultA.class.getName().equals( bizClassLoader.loadClass("defaultA").getName())); System.clearProperty(BIZ_CLASS_LOADER_HOOK_DIR); } } @Test public void testPluginClassLoaderSPI() throws Throwable { PluginClassLoader pluginClassLoader = new PluginClassLoader("mock", (ClassLoaderUtils.getURLs(this.getClass().getClassLoader()))); Assert.assertTrue(TestBizClassLoaderHook.ClassA.class.getName().equals( pluginClassLoader.loadClass("A.A").getName())); Assert.assertTrue(TestBizClassLoaderHook.ClassB.class.getName().equals( pluginClassLoader.loadClass("B").getName())); Assert.assertTrue(pluginClassLoader.getResource("R1").getFile() .endsWith("pluginA_export_resource1.xml")); Assert.assertTrue(pluginClassLoader.getResource("sample-biz.jar").getFile() .endsWith("sample-biz.jar")); Assert.assertTrue(pluginClassLoader.getResource("any").getFile() .endsWith("pluginA_export_resource2.xml")); Enumeration urls = pluginClassLoader.getResources("R2"); Assert.assertTrue(urls.hasMoreElements()); URL url = urls.nextElement(); Assert.assertFalse(urls.hasMoreElements()); Assert.assertTrue(url.getFile().contains("sample-biz.jar")); urls = pluginClassLoader.getResources("test.jar"); Assert.assertTrue(urls.hasMoreElements()); url = urls.nextElement(); Assert.assertFalse(urls.hasMoreElements()); Assert.assertTrue(url.getFile().contains("test.jar")); urls = pluginClassLoader.getResources("any"); Assert.assertTrue(urls.hasMoreElements()); url = urls.nextElement(); Assert.assertFalse(urls.hasMoreElements()); Assert.assertTrue(url.getFile().contains("sample-plugin.jar")); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/classloader/ClassLoaderServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.model.PluginModel; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Field; import java.net.URL; import java.net.URLClassLoader; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import static com.alipay.sofa.ark.common.util.AssertUtils.isFalse; import static com.alipay.sofa.ark.common.util.AssertUtils.isTrue; import static com.alipay.sofa.ark.spi.model.BizState.RESOLVED; import static java.util.Arrays.asList; import static org.junit.Assert.*; import static org.mockito.Mockito.mock; /** * * @author ruoshan * @since 0.1.0 */ public class ClassLoaderServiceTest extends BaseTest { private ClassLoaderService classloaderService; private BizManagerService bizManagerService; private PluginManagerService pluginManagerService; @Before public void before() { super.before(); classloaderService = ArkServiceContainerHolder.getContainer().getService( ClassLoaderService.class); bizManagerService = ArkServiceContainerHolder.getContainer().getService( BizManagerService.class); pluginManagerService = ArkServiceContainerHolder.getContainer().getService( PluginManagerService.class); } @Test public void testIsSunReflect() { assertTrue(classloaderService.isSunReflectClass("sun.reflect.GeneratedMethodAccessor100")); } @Test public void testIsNotSunReflect() { assertFalse(classloaderService.isSunReflectClass("test")); } @Test public void testIsArkSpiClass() { assertTrue(classloaderService.isArkSpiClass("com.alipay.sofa.ark.spi.service.ArkService")); } @Test public void testIsNotArkSpiClass() { assertFalse(classloaderService.isArkSpiClass("test")); } @Test public void testJDKClassLoader() { String sunToolClass = "sun.tools.attach.BsdVirtualMachine"; ClassLoader jdkClassLoader = classloaderService.getJDKClassLoader(); assertNotNull(jdkClassLoader); try { // only when this class can be loaded from system classloader, // then it should be loaded successfully from jdkClassLoader classloaderService.getSystemClassLoader().loadClass(sunToolClass); assertNotNull(jdkClassLoader.loadClass(sunToolClass)); } catch (ClassNotFoundException e) { // ignore } } @Test public void testArkClassLoader() { ClassLoader arkClassLoader = classloaderService.getArkClassLoader(); assertNotNull(arkClassLoader); } @Test public void testSystemClassLoader() { ClassLoader systemClassLoader = classloaderService.getSystemClassLoader(); assertNotNull(systemClassLoader); } @Test public void testAgentClassLoader() throws ClassNotFoundException { ClassLoader agentClassLoader = classloaderService.getAgentClassLoader(); assertNotNull(agentClassLoader); assertTrue(((URLClassLoader) agentClassLoader).getURLs().length == 2); assertNotNull(agentClassLoader.loadClass("SampleClass")); } @Test public void testIsDeniedImportClass() { Biz biz = new BizModel().setBizName("mockBiz").setBizVersion("1.0.0") .setDenyImportPackages("a.c, a.b.c.*, a.b.c").setDenyImportClasses("") .setBizState(RESOLVED); bizManagerService.registerBiz(biz); isFalse(classloaderService.isDeniedImportClass(biz.getIdentity(), "a.c"), "Exception error"); isTrue(classloaderService.isDeniedImportClass(biz.getIdentity(), "a.c.E"), "Exception error"); isFalse(classloaderService.isDeniedImportClass(biz.getIdentity(), "a.c.e.G"), "Exception error"); isTrue(classloaderService.isDeniedImportClass(biz.getIdentity(), "a.b.c.E"), "Exception error"); isTrue(classloaderService.isDeniedImportClass(biz.getIdentity(), "a.b.c.e.G"), "Exception error"); isFalse(classloaderService.isDeniedImportClass(biz.getIdentity(), "a.b.c"), "Exception error"); } @Test public void testIsClassImport() { Plugin plugin = new PluginModel().setPluginName("mockPlugin").setImportClasses(null) .setImportPackages("a.c,a.b.c.*,a.b.c"); pluginManagerService.registerPlugin(plugin); assertTrue(classloaderService.isClassInImport("mockPlugin", "a.c.e")); assertFalse(classloaderService.isClassInImport("mockPlugin", "a.c")); assertFalse(classloaderService.isClassInImport("mockPlugin", "a.c.e.f")); assertFalse(classloaderService.isClassInImport("mockPlugin", "a.b.c")); assertTrue(classloaderService.isClassInImport("mockPlugin", "a.b.c.e")); assertTrue(classloaderService.isClassInImport("mockPlugin", "a.b.c.e.f")); } @Test public void testFindExportClass() { PluginClassLoader pluginClassLoader = new PluginClassLoader("mockPlugin", new URL[] {}); Plugin plugin = new PluginModel().setPluginName("mockPlugin") .setExportPackages("a.b.*,a.f,a.b.f").setExportClasses("a.e.f.G") .setPluginClassLoader(pluginClassLoader).setExportResources(""); pluginManagerService.registerPlugin(plugin); classloaderService.prepareExportClassAndResourceCache(); assertNull(classloaderService.findExportClassLoader("a.b")); assertTrue(pluginClassLoader.equals(classloaderService.findExportClassLoader("a.b.e.f"))); assertTrue(pluginClassLoader.equals(classloaderService.findExportClassLoader("a.f.g"))); assertTrue(pluginClassLoader.equals(classloaderService.findExportClassLoader("a.e.f.G"))); assertTrue(pluginClassLoader.equals(classloaderService.findExportClassLoader("a.b.f.m"))); assertTrue(pluginClassLoader.equals(classloaderService.findExportClassLoader("a.b.f.m.g"))); assertNull(classloaderService.findExportClassLoader("a.f.h.m")); assertNull(classloaderService.findExportClassLoader("a")); pluginManagerService.getPluginsInOrder().remove(plugin); } @Test public void testFindExportResources() { PluginClassLoader pluginClassLoader = new PluginClassLoader("mockPlugin", new URL[] {}); String exportResources = "spring-beans.xsd,*.xsd,com/alipay/sofa/*,xml-test.xml"; Plugin plugin = new PluginModel().setPluginName("mockPlugin").setExportPackages("") .setExportClasses("").setPluginClassLoader(pluginClassLoader) .setExportResources(exportResources); pluginManagerService.registerPlugin(plugin); classloaderService.prepareExportClassAndResourceCache(); Set exportPrefixResourceStems = plugin.getExportPrefixResourceStems(); assertTrue(exportPrefixResourceStems.contains("com/alipay/sofa/")); Set exportSuffixResourceStems = plugin.getExportSuffixResourceStems(); assertTrue(exportSuffixResourceStems.contains(".xsd")); Set resources = plugin.getExportResources(); assertTrue(resources.contains("xml-test.xml")); assertTrue(resources.contains("spring-beans.xsd")); plugin.getExportPrefixResourceStems().clear(); plugin.getExportSuffixResourceStems().clear(); plugin.getExportResources().clear(); pluginManagerService.getPluginsInOrder().remove(plugin); } @Test public void testFindExportResourceClassLoadersInOrder() throws Exception { Field field = ClassLoaderServiceImpl.class .getDeclaredField("exportSuffixStemResourceAndClassLoaderMap"); field.setAccessible(true); ConcurrentHashMap exportPrefixStemResourceAndClassLoaderMap = new ConcurrentHashMap<>(); Plugin plugin = mock(Plugin.class); exportPrefixStemResourceAndClassLoaderMap.put("myaaa", asList(plugin)); field.set(classloaderService, exportPrefixStemResourceAndClassLoaderMap); assertEquals(null, classloaderService.findExportResourceClassLoadersInOrder("myaaa").get(0)); assertNull(classloaderService.getBizClassLoader("aaa:1.0")); assertNull(classloaderService.getPluginClassLoader("aaa:2.0")); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/classloader/CompoundEnumerationTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.biz.BizFactoryService; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.plugin.PluginFactoryService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.NoSuchElementException; public class CompoundEnumerationTest extends BaseTest { private PluginFactoryService pluginFactoryService; private PluginManagerService pluginManagerService; private BizFactoryService bizFactoryService; private BizManagerService bizManagerService; @Before public void before() { super.before(); pluginManagerService = arkServiceContainer.getService(PluginManagerService.class); pluginFactoryService = arkServiceContainer.getService(PluginFactoryService.class); bizFactoryService = arkServiceContainer.getService(BizFactoryService.class); bizManagerService = ArkServiceContainerHolder.getContainer().getService( BizManagerService.class); } @Test(expected = NoSuchElementException.class) public void test() throws IOException { URL bizURL = this.getClass().getClassLoader().getResource("sample-ark-1.0.0-ark-biz.jar"); URL pluginURL = this.getClass().getClassLoader().getResource("sample-plugin.jar"); BizModel bizModel = createTestBizModel("biz A", "1.0.0", BizState.RESOLVED, new URL[] { bizURL, pluginURL }); bizModel.setDenyImportClasses(StringUtils.EMPTY_STRING); bizModel.setDenyImportPackages(StringUtils.EMPTY_STRING); bizModel.setDenyImportResources(StringUtils.EMPTY_STRING); bizManagerService.registerBiz(bizModel); CompoundEnumeration e = (CompoundEnumeration) bizModel.getBizClassLoader() .getResources(Constants.ARK_PLUGIN_MARK_ENTRY); Assert.assertTrue(e.hasMoreElements()); URL url = e.nextElement(); Assert.assertNotNull(url); e.nextElement(); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/classloader/PluginClassLoaderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.model.PluginModel; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.container.testdata.ITest; import com.alipay.sofa.ark.exception.ArkLoaderException; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderService; import com.alipay.sofa.ark.spi.service.plugin.PluginDeployService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.net.URL; import java.util.Collections; import java.util.Enumeration; import java.util.List; import static com.alipay.sofa.ark.common.util.ClassLoaderUtils.getURLs; import static com.alipay.sofa.ark.common.util.ClassUtils.getPackageName; import static com.alipay.sofa.ark.common.util.StringUtils.EMPTY_STRING; import static com.google.common.collect.Sets.newHashSet; import static java.lang.ClassLoader.getSystemClassLoader; import static org.junit.Assert.*; /** * @author ruoshan * @since 0.1.0 */ public class PluginClassLoaderTest extends BaseTest { private URL classPathURL = PluginClassLoaderTest.class.getClassLoader() .getResource(""); private PluginManagerService pluginManagerService; private PluginDeployService pluginDeployService; private ClassLoaderService classloaderService; @Before public void before() { super.before(); pluginManagerService = ArkServiceContainerHolder.getContainer().getService( PluginManagerService.class); pluginDeployService = ArkServiceContainerHolder.getContainer().getService( PluginDeployService.class); classloaderService = ArkServiceContainerHolder.getContainer().getService( ClassLoaderService.class); } @Test public void testExportAndImport() throws Exception { PluginModel pluginA = new PluginModel(); pluginA .setPluginName("plugin A") .setClassPath(new URL[] { classPathURL }) .setImportClasses(EMPTY_STRING) .setImportPackages(EMPTY_STRING) .setExportPackages(getPackageName(ITest.class.getName())) .setExportClasses("") .setExportResources(EMPTY_STRING) .setImportResources(EMPTY_STRING) .setPluginClassLoader( new PluginClassLoader(pluginA.getPluginName(), pluginA.getClassPath())); PluginModel pluginB = new PluginModel(); pluginB .setPluginName("plugin B") .setPriority("1") .setClassPath(new URL[] { classPathURL }) .setImportClasses(ITest.class.getName()) .setImportPackages(EMPTY_STRING) .setExportPackages("") .setExportClasses("") .setExportResources(EMPTY_STRING) .setImportResources(EMPTY_STRING) .setPluginClassLoader( new PluginClassLoader(pluginB.getPluginName(), pluginB.getClassPath())); pluginManagerService.registerPlugin(pluginA); pluginManagerService.registerPlugin(pluginB); classloaderService.prepareExportClassAndResourceCache(); pluginDeployService.deploy(); assertEquals(pluginA.getPluginClassLoader().loadClass(ITest.class.getName()), pluginB .getPluginClassLoader().loadClass(ITest.class.getName())); } @Test public void testExportAndNotImport() throws Exception { PluginModel pluginA = new PluginModel(); pluginA .setPluginName("plugin A") .setClassPath(new URL[] { classPathURL }) .setImportClasses(EMPTY_STRING) .setImportPackages(EMPTY_STRING) .setExportPackages("com.alipay.sofa.ark") .setExportClasses("") .setExportResources(EMPTY_STRING) .setImportResources(EMPTY_STRING) .setPluginClassLoader( new PluginClassLoader(pluginA.getPluginName(), pluginA.getClassPath())); PluginModel pluginB = new PluginModel(); pluginB .setPluginName("plugin B") .setPriority("1") .setClassPath(new URL[] { classPathURL }) .setImportClasses(EMPTY_STRING) .setImportPackages(EMPTY_STRING) .setExportPackages("") .setExportClasses("") .setExportResources(EMPTY_STRING) .setImportResources(EMPTY_STRING) .setPluginClassLoader( new PluginClassLoader(pluginB.getPluginName(), pluginB.getClassPath())); pluginManagerService.registerPlugin(pluginA); pluginManagerService.registerPlugin(pluginB); classloaderService.prepareExportClassAndResourceCache(); pluginDeployService.deploy(); Assert.assertNotEquals(pluginA.getPluginClassLoader().loadClass(ITest.class.getName()), pluginB.getPluginClassLoader().loadClass(ITest.class.getName())); } @Test public void testExportResource() { PluginModel pluginA = new PluginModel(); pluginA .setPluginName("pluginA") .setClassPath(new URL[] { classPathURL }) .setImportClasses(EMPTY_STRING) .setImportPackages(EMPTY_STRING) .setExportPackages(getPackageName(ITest.class.getCanonicalName())) .setExportClasses("") .setImportResources(EMPTY_STRING) .setExportResources("pluginA_export_resource1.xml,pluginA_export_resource2.xml") .setPluginClassLoader( new PluginClassLoader(pluginA.getPluginName(), pluginA.getClassPath())); PluginModel pluginB = new PluginModel(); pluginB .setPluginName("pluginB") .setPriority("1") .setClassPath(new URL[0]) .setImportClasses(EMPTY_STRING) .setImportPackages(EMPTY_STRING) .setExportPackages("") .setExportClasses("") .setImportResources("pluginA_export_resource1.xml") .setExportResources(EMPTY_STRING) .setPluginClassLoader( new PluginClassLoader(pluginB.getPluginName(), pluginB.getClassPath())); pluginManagerService.registerPlugin(pluginA); pluginManagerService.registerPlugin(pluginB); classloaderService.prepareExportClassAndResourceCache(); pluginDeployService.deploy(); assertNotNull(pluginB.getPluginClassLoader().getResource("pluginA_export_resource1.xml")); Assert.assertNull(pluginB.getPluginClassLoader() .getResource("pluginA_export_resource2.xml")); Assert.assertNull(pluginB.getPluginClassLoader().getResource( "pluginA_not_export_resource.xml")); } @Test public void testMultiExportResource() throws Exception { String resourceName = "multi_export.xml"; PluginModel pluginA = new PluginModel(); pluginA .setPluginName("pluginA") .setPriority("100") .setClassPath(new URL[] { classPathURL }) .setImportClasses(EMPTY_STRING) .setImportPackages(EMPTY_STRING) .setExportPackages(getPackageName(ITest.class.getCanonicalName())) .setExportClasses("") .setImportResources(EMPTY_STRING) .setExportResources(resourceName) .setPluginClassLoader( new PluginClassLoader(pluginA.getPluginName(), pluginA.getClassPath())); PluginModel pluginB = new PluginModel(); pluginB .setPluginName("pluginB") .setPriority("1") .setClassPath(new URL[] { classPathURL }) .setImportClasses(EMPTY_STRING) .setImportPackages(EMPTY_STRING) .setExportPackages("") .setExportClasses("") .setImportResources(EMPTY_STRING) .setExportResources(resourceName) .setPluginClassLoader( new PluginClassLoader(pluginB.getPluginName(), pluginB.getClassPath())); PluginModel pluginC = new PluginModel(); pluginC .setPluginName("pluginC") .setPriority("1000") .setClassPath(new URL[] { classPathURL }) .setImportClasses(EMPTY_STRING) .setImportPackages(EMPTY_STRING) .setExportPackages("") .setExportClasses("") .setImportResources(EMPTY_STRING) .setExportResources(resourceName) .setPluginClassLoader( new PluginClassLoader(pluginC.getPluginName(), pluginC.getClassPath())); PluginModel pluginD = new PluginModel(); pluginD .setPluginName("pluginD") .setClassPath(new URL[0]) .setImportClasses(EMPTY_STRING) .setImportPackages(EMPTY_STRING) .setExportPackages("") .setExportClasses("") .setImportResources(resourceName) .setExportResources(EMPTY_STRING) .setPluginClassLoader( new PluginClassLoader(pluginD.getPluginName(), pluginD.getClassPath())); pluginManagerService.registerPlugin(pluginA); pluginManagerService.registerPlugin(pluginB); pluginManagerService.registerPlugin(pluginC); pluginManagerService.registerPlugin(pluginD); classloaderService.prepareExportClassAndResourceCache(); pluginDeployService.deploy(); Enumeration urlEnumeration = pluginD.getPluginClassLoader().getResources(resourceName); assertEquals(3, Collections.list(urlEnumeration).size()); List classLoaders = classloaderService .findExportResourceClassLoadersInOrder(resourceName); assertEquals(3, classLoaders.size()); assertEquals(pluginB.getPluginClassLoader(), classLoaders.get(0)); assertEquals(pluginA.getPluginClassLoader(), classLoaders.get(1)); assertEquals(pluginC.getPluginClassLoader(), classLoaders.get(2)); } @Test public void testExportResourceStems() { PluginModel pluginA = new PluginModel(); pluginA .setPluginName("pluginA") .setPriority("100") .setClassPath(new URL[] { classPathURL }) .setImportClasses(EMPTY_STRING) .setImportPackages(EMPTY_STRING) .setExportPackages(EMPTY_STRING) .setExportClasses(EMPTY_STRING) .setImportResources(EMPTY_STRING) .setExportResources("export/folderA/*,export/folderB/*") .setPluginClassLoader( new PluginClassLoader(pluginA.getPluginName(), pluginA.getClassPath())); PluginModel pluginB = new PluginModel(); pluginB .setPluginName("pluginB") .setPriority("1") .setClassPath(new URL[0]) .setImportClasses(EMPTY_STRING) .setImportPackages(EMPTY_STRING) .setExportPackages(EMPTY_STRING) .setExportClasses(EMPTY_STRING) .setImportResources("export/folderA/*,export/folderB/test3.xml") .setExportResources(EMPTY_STRING) .setPluginClassLoader( new PluginClassLoader(pluginB.getPluginName(), pluginB.getClassPath())); pluginManagerService.registerPlugin(pluginA); pluginManagerService.registerPlugin(pluginB); classloaderService.prepareExportClassAndResourceCache(); pluginDeployService.deploy(); String testResource1 = "export/folderA/test1.xml"; String testResource2 = "export/folderA/test2.xml"; String testResource3 = "export/folderB/test3.xml"; String testResource4 = "export/folderB/test4.xml"; assertEquals(pluginA.getPluginClassLoader().getResource(testResource1), pluginB .getPluginClassLoader().getResource(testResource1)); assertEquals(pluginA.getPluginClassLoader().getResource(testResource2), pluginB .getPluginClassLoader().getResource(testResource2)); assertEquals(pluginA.getPluginClassLoader().getResource(testResource3), pluginB .getPluginClassLoader().getResource(testResource3)); // export/folderB/test4.xml not import Assert.assertNull(pluginB.getPluginClassLoader().getResource(testResource4)); } @Test public void testLoadClassFromAgentClassLoader() throws ClassNotFoundException { PluginModel mockPlugin = new PluginModel(); mockPlugin .setPluginName("Mock plugin") .setClassPath(new URL[] {}) .setImportClasses(EMPTY_STRING) .setImportPackages(EMPTY_STRING) .setExportPackages(ITest.class.getCanonicalName()) .setPluginClassLoader( new PluginClassLoader(mockPlugin.getPluginName(), mockPlugin.getClassPath())); pluginManagerService.registerPlugin(mockPlugin); PluginClassLoader pluginClassLoader = (PluginClassLoader) mockPlugin.getPluginClassLoader(); assertNotNull(pluginClassLoader.loadClass("SampleClass", false)); Class clazz = pluginClassLoader.loadClass(ArkClient.class.getCanonicalName()); assertTrue(clazz.getClassLoader().equals(classloaderService.getArkClassLoader())); } @Test public void testGetJdkResource() throws IOException { PluginModel mockPlugin = new PluginModel(); mockPlugin .setPluginName("Mock plugin") .setClassPath(new URL[] {}) .setImportResources(EMPTY_STRING) .setImportClasses(EMPTY_STRING) .setImportPackages(EMPTY_STRING) .setExportPackages(ITest.class.getCanonicalName()) .setPluginClassLoader( new PluginClassLoader(mockPlugin.getPluginName(), mockPlugin.getClassPath())); pluginManagerService.registerPlugin(mockPlugin); ClassLoader cl = mockPlugin.getPluginClassLoader(); // String name = "META-INF/services/javax.script.ScriptEngineFactory"; String name = "javax/lang/model/element/Modifier.class"; URL res1 = cl.getResource(name); assertNotNull(res1); URL res2 = getSystemClassLoader().getResource(name); assertNotNull(res2); assertEquals(res2, res1); Enumeration enu1 = cl.getResources(name); assertTrue(enu1.hasMoreElements()); Enumeration enu2 = getSystemClassLoader().getResources(name); assertEquals(newHashSet(Collections.list(enu2)), newHashSet(Collections.list(enu1))); } @Test public void testSlashResource() throws Throwable { ClassLoader classLoader = this.getClass().getClassLoader(); PluginClassLoader pluginClassLoader = new PluginClassLoader("pluginName", getURLs(classLoader)); PluginModel mockPlugin = new PluginModel(); mockPlugin.setPluginName("pluginName").setClassPath(new URL[] {}) .setImportResources(EMPTY_STRING).setImportClasses(EMPTY_STRING) .setImportPackages(EMPTY_STRING) .setExportPackages(getPackageName(ITest.class.getCanonicalName())) .setExportClasses(EMPTY_STRING).setPluginClassLoader(pluginClassLoader); pluginManagerService.registerPlugin(mockPlugin); URL url = pluginClassLoader.getResource(""); assertNotNull(url); assertEquals(url, this.getClass().getResource("/")); } @Test(expected = ArkLoaderException.class) public void testLoadClassInternalWithSunClass() throws Exception { PluginClassLoader pluginClassLoader = new PluginClassLoader("a", new URL[] { this .getClass().getResource("/") }); assertEquals("a", pluginClassLoader.getPluginName()); pluginClassLoader.loadClassInternal("sun.reflect.GeneratedMethodAccessor", true); } @Test(expected = ArkLoaderException.class) public void testLoadClassInternalWithResolve() throws Exception { PluginClassLoader pluginClassLoader = new PluginClassLoader("a", new URL[] { this .getClass().getResource("/") }); assertEquals("a", pluginClassLoader.getPluginName()); pluginClassLoader.loadClassInternal("java.lang.a", true); } @Test(expected = ArkLoaderException.class) public void testPreLoadClassWithException() throws Exception { PluginClassLoader pluginClassLoader = new PluginClassLoader("a", new URL[] { this .getClass().getResource("/") }); pluginClassLoader.preLoadClass("a"); } @Test(expected = ArkLoaderException.class) public void testPostLoadClassWithException() throws Exception { PluginClassLoader pluginClassLoader = new PluginClassLoader("a", new URL[] { this .getClass().getResource("/") }); pluginClassLoader.postLoadClass("a"); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/classloader/hook/AbstractClassLoaderHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader.hook; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderHook; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderService; import com.alipay.sofa.ark.container.service.classloader.CompoundEnumeration; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; /** * @author qilong.zql * @since 0.6.0 */ public class AbstractClassLoaderHook implements ClassLoaderHook { @Override public Class preFindClass(String name, ClassLoaderService classLoaderService, T t) { if ("A.A".equals(name)) { return TestBizClassLoaderHook.ClassA.class; } return null; } @Override public Class postFindClass(String name, ClassLoaderService classLoaderService, T t) { return TestBizClassLoaderHook.ClassB.class; } @Override public URL preFindResource(String name, ClassLoaderService classLoaderService, T t) { if ("R1".equals(name)) { return classLoaderService.getArkClassLoader().getResource( "pluginA_export_resource1.xml"); } return null; } @Override public URL postFindResource(String name, ClassLoaderService classLoaderService, T t) { if ("any".equals(name)) { return classLoaderService.getArkClassLoader().getResource( "pluginA_export_resource2.xml"); } return null; } @Override public Enumeration preFindResources(String name, ClassLoaderService classLoaderService, T t) throws IOException { if ("R2".equals(name)) { return classLoaderService.getArkClassLoader().getResources("sample-biz.jar"); } List> enumerationList = new ArrayList<>(); return new CompoundEnumeration<>(enumerationList.toArray(new Enumeration[] {})); } @Override public Enumeration postFindResources(String name, ClassLoaderService classLoaderService, T t) throws IOException { if ("any".equals(name)) { return classLoaderService.getArkClassLoader().getResources("sample-plugin.jar"); } List> enumerationList = new ArrayList<>(); return new CompoundEnumeration<>(enumerationList.toArray(new Enumeration[] {})); } public static class ClassA { } public static class ClassB { } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/classloader/hook/TestBizClassLoaderHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader.hook; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.service.extension.Extension; /** * @author qilong.zql * @since 0.6.0 */ @Extension(Constants.BIZ_CLASS_LOADER_HOOK) public class TestBizClassLoaderHook extends AbstractClassLoaderHook { public class ClassA { } public class ClassB { } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/classloader/hook/TestDefaultBizClassLoaderHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader.hook; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderHook; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderService; import com.alipay.sofa.ark.spi.service.extension.Extension; import java.io.IOException; import java.net.URL; import java.util.Enumeration; /** * @author jinle.xjl * @since 1.1.7 */ @Extension(value = Constants.BIZ_CLASS_LOADER_HOOK, order = 99) public class TestDefaultBizClassLoaderHook implements ClassLoaderHook { @Override public Class preFindClass(String name, ClassLoaderService classLoaderService, Biz biz) throws ClassNotFoundException { if ("defaultA".equals(name)) { return ClassDefaultA.class; } return null; } @Override public Class postFindClass(String name, ClassLoaderService classLoaderService, Biz biz) throws ClassNotFoundException { if ("defaultB".equals(name)) { return ClassDefaultB.class; } return null; } @Override public URL preFindResource(String name, ClassLoaderService classLoaderService, Biz biz) { return null; } @Override public URL postFindResource(String name, ClassLoaderService classLoaderService, Biz biz) { return null; } @Override public Enumeration preFindResources(String name, ClassLoaderService classLoaderService, Biz biz) throws IOException { return null; } @Override public Enumeration postFindResources(String name, ClassLoaderService classLoaderService, Biz biz) throws IOException { return null; } public class ClassDefaultA { } public class ClassDefaultB { } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/classloader/hook/TestPluginClassLoaderHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.classloader.hook; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.extension.Extension; /** * @author qilong.zql * @since 0.6.0 */ @Extension(Constants.PLUGIN_CLASS_LOADER_HOOK) public class TestPluginClassLoaderHook extends AbstractClassLoaderHook { } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/event/EventAdminServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.event; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.spi.event.biz.AfterBizStopEvent; import com.alipay.sofa.ark.spi.event.biz.BeforeBizStopEvent; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import com.alipay.sofa.ark.spi.service.event.EventHandler; import org.junit.Assert; import org.junit.Test; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @author qilong.zql * @since 0.4.0 */ public class EventAdminServiceTest extends BaseTest { private static int mark = 5; static Map map = new HashMap(); @Test public void test() throws Throwable { try { Field field = EventAdminServiceImpl.class.getDeclaredField("SUBSCRIBER_MAP"); field.setAccessible(true); map = (Map) field.get(null); } catch (Throwable throwable) { Assert.assertNull(throwable); } EventAdminService eventAdminService = ArkServiceContainerHolder.getContainer().getService( EventAdminService.class); eventAdminService.register(new LowPriorityMockEventHandler()); eventAdminService.register(new HighPriorityMockEventHandler()); eventAdminService.register(new BeforeBizStopEventHandler()); ClassLoader bizClassLoader = getClass().getClassLoader(); Biz biz = new BizModel().setBizState(BizState.DEACTIVATED).setBizName("mock name") .setBizVersion("mock name").setClassLoader(bizClassLoader); Assert.assertNotNull(map.get(bizClassLoader)); biz.stop(); Assert.assertNull(map.get(bizClassLoader)); Assert.assertTrue(mark == 50); EventHandler eventHandler = new LowPriorityMockEventHandler(); eventAdminService.register(eventHandler); Assert.assertNotNull(map.get(bizClassLoader)); eventAdminService.unRegister(eventHandler); Assert.assertFalse(((Set) map.get(bizClassLoader)).contains(eventHandler)); } class HighPriorityMockEventHandler implements EventHandler { @Override public void handleEvent(BeforeBizStopEvent event) { mark *= mark; } @Override public int getPriority() { return 10; } } class LowPriorityMockEventHandler implements EventHandler { @Override public void handleEvent(BeforeBizStopEvent event) { mark += mark; } @Override public int getPriority() { return 100; } } class BeforeBizStopEventHandler implements EventHandler { @Override public void handleEvent(AfterBizStopEvent event) { map.remove(getClass().getClassLoader()); } @Override public int getPriority() { return 100; } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/event/EventTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.event; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.model.PluginModel; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.event.AfterFinishDeployEvent; import com.alipay.sofa.ark.spi.event.AfterFinishStartupEvent; import com.alipay.sofa.ark.spi.event.biz.AfterBizStartupEvent; import com.alipay.sofa.ark.spi.event.biz.AfterBizStopEvent; import com.alipay.sofa.ark.spi.event.biz.AfterBizSwitchEvent; import com.alipay.sofa.ark.spi.event.biz.BeforeBizRecycleEvent; import com.alipay.sofa.ark.spi.event.biz.BeforeBizStartupEvent; import com.alipay.sofa.ark.spi.event.biz.BeforeBizStopEvent; import com.alipay.sofa.ark.spi.event.biz.BeforeBizSwitchEvent; import com.alipay.sofa.ark.spi.event.biz.AfterBizStartupFailedEvent; import com.alipay.sofa.ark.spi.event.plugin.AfterPluginStartupEvent; import com.alipay.sofa.ark.spi.event.plugin.AfterPluginStopEvent; import com.alipay.sofa.ark.spi.event.plugin.BeforePluginStartupEvent; import com.alipay.sofa.ark.spi.event.plugin.BeforePluginStopEvent; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import com.alipay.sofa.ark.spi.service.event.EventHandler; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.List; /** * test for specified event type * * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/5 10:39 AM * @since **/ public class EventTest extends BaseTest { EventAdminService eventAdminService; static List result = new ArrayList<>(); @Before public void before() { super.before(); eventAdminService = ArkServiceContainerHolder.getContainer().getService( EventAdminService.class); eventAdminService.register(new AfterBizStartupEventHandler()); eventAdminService.register(new AfterBizStopEventHandler()); eventAdminService.register(new AfterBizSwitchEventHandler()); eventAdminService.register(new BeforeBizStartupEventHandler()); eventAdminService.register(new BeforeBizStopEventHandler()); eventAdminService.register(new BeforeBizSwitchEventHandler()); eventAdminService.register(new AfterPluginStartupEventHandler()); eventAdminService.register(new BeforePluginStopEventHandler()); eventAdminService.register(new AfterPluginStopEventHandler()); eventAdminService.register(new BeforePluginStartupEventHandler()); eventAdminService.register(new AfterFinishDeployEventHandler()); eventAdminService.register(new AfterFinishStartupEventHandler()); eventAdminService.register(new BeforeBizRecycleEventEventHandler()); eventAdminService.register(new TestArkEventHandler()); eventAdminService.register(new BizFailedEventHandler()); } @After public void after() { result.clear(); super.after(); } @Test public void testEvent() { Biz biz = new BizModel().setBizName("test-biz").setBizVersion("1.0.0") .setBizState(BizState.RESOLVED); Plugin plugin = new PluginModel().setPluginName("test-plugin").setVersion("1.0.0"); eventAdminService.sendEvent(new AfterBizStartupEvent(biz)); Assert.assertTrue(result.get(0).equalsIgnoreCase( Constants.BIZ_EVENT_TOPIC_AFTER_INVOKE_BIZ_START)); eventAdminService.sendEvent(new BeforeBizStartupEvent(biz)); Assert.assertTrue(result.get(1).equalsIgnoreCase( Constants.BIZ_EVENT_TOPIC_BEFORE_INVOKE_BIZ_START)); eventAdminService.sendEvent(new BeforeBizStopEvent(biz)); Assert.assertTrue(result.get(2).equalsIgnoreCase( Constants.BIZ_EVENT_TOPIC_BEFORE_INVOKE_BIZ_STOP)); eventAdminService.sendEvent(new AfterBizStopEvent(biz)); Assert.assertTrue(result.get(3).equalsIgnoreCase( Constants.BIZ_EVENT_TOPIC_AFTER_INVOKE_BIZ_STOP)); eventAdminService.sendEvent(new BeforeBizSwitchEvent(biz)); Assert.assertTrue(result.get(4).equalsIgnoreCase( Constants.BIZ_EVENT_TOPIC_BEFORE_INVOKE_BIZ_SWITCH)); eventAdminService.sendEvent(new AfterBizSwitchEvent(biz)); Assert.assertTrue(result.get(5).equalsIgnoreCase( Constants.BIZ_EVENT_TOPIC_AFTER_INVOKE_BIZ_SWITCH)); eventAdminService.sendEvent(new AfterPluginStartupEvent(plugin)); Assert.assertTrue(result.get(6).equalsIgnoreCase( Constants.PLUGIN_EVENT_TOPIC_AFTER_INVOKE_PLUGIN_START)); eventAdminService.sendEvent(new AfterPluginStopEvent(plugin)); Assert.assertTrue(result.get(7).equalsIgnoreCase( Constants.PLUGIN_EVENT_TOPIC_AFTER_INVOKE_PLUGIN_STOP)); eventAdminService.sendEvent(new BeforePluginStartupEvent(plugin)); Assert.assertTrue(result.get(8).equalsIgnoreCase( Constants.PLUGIN_EVENT_TOPIC_BEFORE_INVOKE_PLUGIN_START)); eventAdminService.sendEvent(new BeforePluginStopEvent(plugin)); Assert.assertTrue(result.get(9).equalsIgnoreCase( Constants.PLUGIN_EVENT_TOPIC_BEFORE_INVOKE_PLUGIN_STOP)); eventAdminService.sendEvent(new AfterFinishDeployEvent()); Assert.assertTrue(result.get(10).equalsIgnoreCase( Constants.ARK_EVENT_TOPIC_AFTER_FINISH_DEPLOY_STAGE)); eventAdminService.sendEvent(new AfterFinishStartupEvent()); Assert.assertTrue(result.get(11).equalsIgnoreCase( Constants.ARK_EVENT_TOPIC_AFTER_FINISH_STARTUP_STAGE)); eventAdminService.sendEvent(new TestArkEvent("")); Assert.assertTrue(result.get(12).equalsIgnoreCase("test-ark")); eventAdminService.sendEvent(new BeforeBizRecycleEvent(biz)); Assert.assertTrue(result.get(13).equalsIgnoreCase( Constants.BIZ_EVENT_TOPIC_BEFORE_RECYCLE_BIZ)); eventAdminService.sendEvent(new AfterBizStartupFailedEvent(biz, new Throwable())); Assert.assertTrue(result.get(14).equalsIgnoreCase( Constants.BIZ_EVENT_TOPIC_AFTER_BIZ_FAILED)); } static class TestArkEvent extends AbstractArkEvent { public TestArkEvent(Object source) { super(source); this.topic = "test-ark"; } } static class TestArkEventHandler implements EventHandler { @Override public void handleEvent(TestArkEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class AfterBizStartupEventHandler implements EventHandler { @Override public void handleEvent(AfterBizStartupEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class BeforeBizRecycleEventEventHandler implements EventHandler { @Override public void handleEvent(BeforeBizRecycleEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class AfterBizStopEventHandler implements EventHandler { @Override public void handleEvent(AfterBizStopEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class AfterBizSwitchEventHandler implements EventHandler { @Override public void handleEvent(AfterBizSwitchEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class BeforeBizStartupEventHandler implements EventHandler { @Override public void handleEvent(BeforeBizStartupEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class BeforeBizStopEventHandler implements EventHandler { @Override public void handleEvent(BeforeBizStopEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class BeforeBizSwitchEventHandler implements EventHandler { @Override public void handleEvent(BeforeBizSwitchEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class AfterPluginStartupEventHandler implements EventHandler { @Override public void handleEvent(AfterPluginStartupEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class BeforePluginStopEventHandler implements EventHandler { @Override public void handleEvent(BeforePluginStopEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class AfterPluginStopEventHandler implements EventHandler { @Override public void handleEvent(AfterPluginStopEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class BeforePluginStartupEventHandler implements EventHandler { @Override public void handleEvent(BeforePluginStartupEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class AfterFinishDeployEventHandler implements EventHandler { @Override public void handleEvent(AfterFinishDeployEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class AfterFinishStartupEventHandler implements EventHandler { @Override public void handleEvent(AfterFinishStartupEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class BizFailedEventHandler implements EventHandler { @Override public void handleEvent(AfterBizStartupFailedEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/event/GlobalEventHandlerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.event; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.model.PluginModel; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.event.ArkEvent; import com.alipay.sofa.ark.spi.event.biz.AfterBizStartupEvent; import com.alipay.sofa.ark.spi.event.plugin.BeforePluginStartupEvent; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import com.alipay.sofa.ark.spi.service.event.EventHandler; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.List; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/19 3:46 PM * @since **/ public class GlobalEventHandlerTest extends BaseTest { EventAdminService eventAdminService; ArkEventHandler arkEventHandler = new ArkEventHandler(); ArkEventHandler1 arkEventHandler1 = new ArkEventHandler1(); AbstractArkEventHandler abstractArkEventHandler = new AbstractArkEventHandler(); static List result = new ArrayList<>(); @Before public void before() { super.before(); eventAdminService = ArkServiceContainerHolder.getContainer().getService( EventAdminService.class); eventAdminService.register(arkEventHandler); eventAdminService.register(arkEventHandler1); eventAdminService.register(abstractArkEventHandler); } @After public void after() { result.clear(); eventAdminService.unRegister(arkEventHandler); eventAdminService.unRegister(arkEventHandler1); eventAdminService.unRegister(abstractArkEventHandler); arkEventHandler = null; arkEventHandler1 = null; super.after(); } @Test public void testEvent() { Biz biz = new BizModel().setBizName("test-biz").setBizVersion("1.0.0") .setBizState(BizState.RESOLVED); Plugin plugin = new PluginModel().setPluginName("test-plugin").setVersion("1.0.0"); eventAdminService.sendEvent(new AfterBizStartupEvent(biz)); Assert.assertTrue(result.size() == 3); Assert.assertTrue(result.contains("AbstractArkEvent->AfterBizStartupEvent")); eventAdminService.sendEvent(new BeforePluginStartupEvent(plugin)); Assert.assertTrue(result.size() == 5); // test for ArkEvent.class.isAssignableFrom(event.getClass() eventAdminService.sendEvent(new ArkEvent() { @Override public String getTopic() { return "ark-event"; } }); Assert.assertTrue(result.size() == 7); } static class ArkEventHandler implements EventHandler { @Override public void handleEvent(ArkEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class ArkEventHandler1 implements EventHandler { @Override public void handleEvent(ArkEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } static class AbstractArkEventHandler implements EventHandler { @Override public void handleEvent(AbstractArkEvent event) { if (event instanceof AfterBizStartupEvent) { result.add("AbstractArkEvent->AfterBizStartupEvent"); } } @Override public int getPriority() { return 0; } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/event/MultiEventTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.event; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AbstractArkEvent; import com.alipay.sofa.ark.spi.event.AfterFinishStartupEvent; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import com.alipay.sofa.ark.spi.service.event.EventHandler; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.List; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2019/11/19 11:43 AM * @since **/ public class MultiEventTest extends BaseTest { EventAdminService eventAdminService; static List result = new ArrayList<>(); @Before public void before() { super.before(); eventAdminService = ArkServiceContainerHolder.getContainer().getService( EventAdminService.class); eventAdminService.register(new MultiAfterFinishStartupEventHandler()); eventAdminService.register(new AbstractEventHandler()); } @After public void after() { result.clear(); super.after(); } @Test public void testEvent() { eventAdminService.sendEvent(new AfterFinishStartupEvent()); Assert.assertTrue(result.get(0).equalsIgnoreCase( Constants.ARK_EVENT_TOPIC_AFTER_FINISH_STARTUP_STAGE)); } static class MultiAfterFinishStartupEventHandler implements EventHandler, OtherHandler { @Override public void handleEvent(AfterFinishStartupEvent event) { result.add(event.getTopic()); } @Override public int getPriority() { return 0; } } interface OtherHandler { } static class TestEvent { public static class SubTestEvent extends TestEvent { } } static class AbstractEventHandler implements EventHandler { @Override public void handleEvent(AbstractArkEvent event) { if (event instanceof AfterFinishStartupEvent) { result.add(event.getTopic()); } } @Override public int getPriority() { return 0; } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/extension/ExtensionClassTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.extension; import com.alipay.sofa.ark.container.service.extension.spi.SingletonService; import com.alipay.sofa.ark.container.service.extension.spi.impl.SingletonServiceImpl; import com.alipay.sofa.ark.spi.service.extension.Extensible; import com.alipay.sofa.ark.spi.service.extension.Extension; import com.alipay.sofa.ark.spi.service.extension.ExtensionClass; import org.junit.Assert; import org.junit.Test; /** * @author qilong.zql * @since 0.6.0 */ public class ExtensionClassTest { @Test public void testExtensionClass() { Extensible extensible = SingletonService.class.getAnnotation(Extensible.class); Extension extension = SingletonServiceImpl.class.getAnnotation(Extension.class); ExtensionClass extensionClass = new ExtensionClass(); extensionClass.setExtensible(extensible); extensionClass.setExtension(extension); extensionClass.setInterfaceClass(SingletonService.class); extensionClass.setImplementClass(SingletonServiceImpl.class); Object object1 = extensionClass.getObject(); Object object2 = extensionClass.getObject(); Object singleton = extensionClass.getSingleton(); Assert.assertTrue(object1.equals(object2)); Assert.assertTrue(object1.equals(singleton)); Assert.assertEquals(200, extensionClass.getPriority()); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/extension/ExtensionServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.extension; import com.alipay.sofa.ark.container.ArkContainer; import com.alipay.sofa.ark.container.ArkContainerTest; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.model.PluginModel; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.container.service.extension.spi.ServiceA; import com.alipay.sofa.ark.container.service.extension.spi.ServiceB; import com.alipay.sofa.ark.container.service.extension.spi.ServiceC; import com.alipay.sofa.ark.container.service.extension.spi.ServiceD; import com.alipay.sofa.ark.container.service.extension.spi.impl.ServiceBImpl4; import com.alipay.sofa.ark.container.service.extension.spi.impl.ServiceBImpl3; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.service.extension.ArkServiceLoader; import com.alipay.sofa.ark.spi.service.extension.Extensible; import com.alipay.sofa.ark.spi.service.extension.Extension; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import org.junit.Assert; import org.junit.Test; import java.net.URL; /** * @author qilong.zql * @since 0.6.0 */ public class ExtensionServiceTest extends BaseTest { private URL jarURL = ArkContainerTest.class.getClassLoader().getResource("test.jar"); ArkContainer arkContainer; @Override public void before() { String[] args = new String[] { "-Ajar=" + jarURL.toExternalForm() }; arkContainer = (ArkContainer) ArkContainer.main(args); } @Override public void after() { arkContainer.stop(); } @Test public void testExtensionServiceNotInstance() { PluginManagerService pluginManagerService = ArkServiceContainerHolder.getContainer() .getService(PluginManagerService.class); PluginModel pluginModel = new PluginModel().setPluginClassLoader( this.getClass().getClassLoader()).setPluginName("mock-plugin"); pluginManagerService.registerPlugin(pluginModel); try { ArkServiceLoader.loadExtensionFromArkPlugin(ServiceA.class, "", "mock-plugin"); } catch (ArkRuntimeException ex) { Assert.assertTrue(ex.getMessage().contains("not type of")); } } @Test public void testNotExtensibleService() { PluginManagerService pluginManagerService = ArkServiceContainerHolder.getContainer() .getService(PluginManagerService.class); PluginModel pluginModel = new PluginModel().setPluginClassLoader( this.getClass().getClassLoader()).setPluginName("mock-plugin"); pluginManagerService.registerPlugin(pluginModel); try { ArkServiceLoader.loadExtensionFromArkPlugin(ServiceC.class, "", "mock-plugin"); } catch (ArkRuntimeException ex) { Assert.assertTrue(ex.getMessage().contains( String.format("is not annotated by %s.", Extensible.class))); } } @Test public void testNoExtensionAnnotation() { PluginManagerService pluginManagerService = ArkServiceContainerHolder.getContainer() .getService(PluginManagerService.class); PluginModel pluginModel = new PluginModel().setPluginClassLoader( this.getClass().getClassLoader()).setPluginName("mock-plugin"); pluginManagerService.registerPlugin(pluginModel); try { ArkServiceLoader.loadExtensionFromArkPlugin(ServiceD.class, "", "mock-plugin"); } catch (ArkRuntimeException ex) { Assert.assertTrue(ex.getMessage().contains( String.format("is not annotated by %s.", Extension.class))); } } @Test public void testExtensionServiceLoader() { PluginManagerService pluginManagerService = ArkServiceContainerHolder.getContainer() .getService(PluginManagerService.class); PluginModel pluginModel = new PluginModel().setPluginClassLoader( this.getClass().getClassLoader()).setPluginName("mock-plugin"); pluginManagerService.registerPlugin(pluginModel); ServiceB impl1 = ArkServiceLoader.loadExtensionFromArkPlugin(ServiceB.class, "type1", "mock-plugin"); Assert.assertTrue(impl1 instanceof ServiceBImpl3); ServiceB impl2 = ArkServiceLoader.loadExtensionFromArkPlugin(ServiceB.class, "type2", "mock-plugin"); Assert.assertTrue(impl2 instanceof ServiceBImpl4); ServiceB impl3 = ArkServiceLoader.loadExtensionFromArkPlugin(ServiceB.class, "type1", "mock-plugin"); Assert.assertTrue(impl3 instanceof ServiceBImpl3); ServiceB impl4 = ArkServiceLoader.loadExtensionFromArkPlugin(ServiceB.class, "type2", "mock-plugin"); Assert.assertTrue(impl4 instanceof ServiceBImpl4); Assert.assertFalse(impl1 == impl3); Assert.assertFalse(impl2 == impl4); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/extension/spi/ServiceA.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.extension.spi; import com.alipay.sofa.ark.spi.service.extension.Extensible; /** * @author qilong.zql * @since 0.6.0 */ @Extensible(file = "serviceA") public interface ServiceA { String service(); } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/extension/spi/ServiceB.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.extension.spi; import com.alipay.sofa.ark.spi.service.extension.Extensible; /** * @author qilong.zql * @since 0.6.0 */ @Extensible(singleton = false) public interface ServiceB { String service(); } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/extension/spi/ServiceC.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.extension.spi; /** * @author qilong.zql * @since 0.6.0 */ public interface ServiceC { String service(); } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/extension/spi/ServiceD.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.extension.spi; import com.alipay.sofa.ark.spi.service.extension.Extensible; /** * @author qilong.zql * @since 0.6.0 */ @Extensible(file = "serviceD") public interface ServiceD { String service(); } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/extension/spi/SingletonService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.extension.spi; import com.alipay.sofa.ark.spi.service.extension.Extensible; /** * @author qilong.zql * @since 0.6.0 */ @Extensible public interface SingletonService { String service(); } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/extension/spi/impl/ServiceBImpl1.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.extension.spi.impl; import com.alipay.sofa.ark.container.service.extension.spi.ServiceB; import com.alipay.sofa.ark.spi.service.extension.Extension; /** * @author qilong.zql * @since 0.6.0 */ @Extension(value = "type1", order = 200) public class ServiceBImpl1 implements ServiceB { @Override public String service() { return "ServiceBImpl1"; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/extension/spi/impl/ServiceBImpl2.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.extension.spi.impl; import com.alipay.sofa.ark.container.service.extension.spi.ServiceB; import com.alipay.sofa.ark.spi.service.extension.Extension; /** * @author qilong.zql * @since 0.6.0 */ @Extension(value = "type1", order = 300) public class ServiceBImpl2 implements ServiceB { @Override public String service() { return "ServiceBImpl2"; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/extension/spi/impl/ServiceBImpl3.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.extension.spi.impl; import com.alipay.sofa.ark.container.service.extension.spi.ServiceB; import com.alipay.sofa.ark.spi.service.extension.Extension; /** * @author qilong.zql * @since 0.6.0 */ @Extension(value = "type1", order = 50) public class ServiceBImpl3 implements ServiceB { @Override public String service() { return "ServiceBImpl3"; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/extension/spi/impl/ServiceBImpl4.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.extension.spi.impl; import com.alipay.sofa.ark.container.service.extension.spi.ServiceB; import com.alipay.sofa.ark.spi.service.extension.Extension; /** * @author qilong.zql * @since 0.6.0 */ @Extension(value = "type2", order = 10) public class ServiceBImpl4 implements ServiceB { @Override public String service() { return "ServiceBImpl4"; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/extension/spi/impl/ServiceDImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.extension.spi.impl; import com.alipay.sofa.ark.container.service.extension.spi.ServiceD; /** * @author qilong.zql * @since 0.6.0 */ public class ServiceDImpl implements ServiceD { @Override public String service() { return "ServiceD"; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/extension/spi/impl/SingletonServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.extension.spi.impl; import com.alipay.sofa.ark.container.service.extension.spi.SingletonService; import com.alipay.sofa.ark.spi.service.extension.Extension; /** * @author qilong.zql * @since 0.6.0 */ @Extension(value = "singletonService", order = 200) public class SingletonServiceImpl implements SingletonService { @Override public String service() { return "SingletonService"; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/injection/InjectionServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.injection; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.registry.ContainerServiceProvider; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.spi.service.ArkInject; import com.alipay.sofa.ark.spi.service.biz.BizFactoryService; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderService; import com.alipay.sofa.ark.spi.service.registry.RegistryService; import org.junit.Assert; import org.junit.Test; /** * @author qilong.zql * @since 0.4.0 */ public class InjectionServiceTest extends BaseTest { @Test public void test() { RegistryService registryService = ArkServiceContainerHolder.getContainer().getService( RegistryService.class); PluginMockService pluginMockService = new PluginMockService(); registryService.publishService(PluginMockService.class, pluginMockService, new ContainerServiceProvider()); Assert.assertNotNull(pluginMockService.getBizFactoryService()); Assert.assertNotNull(pluginMockService.getBizManagerService()); Assert.assertNull(pluginMockService.getClassLoaderService()); } public class PluginMockService { @ArkInject private BizManagerService bizManagerService; @ArkInject BizFactoryService bizFactoryService; @ArkInject public ClassLoaderService classloaderService; public BizManagerService getBizManagerService() { return bizManagerService; } public BizFactoryService getBizFactoryService() { return bizFactoryService; } public ClassLoaderService getClassLoaderService() { return classloaderService; } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/pipeline/HandleArchiveTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.pipeline; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.model.PluginModel; import com.alipay.sofa.ark.container.pipeline.HandleArchiveStage; import org.junit.Assert; import org.junit.Test; import static com.alipay.sofa.ark.spi.constant.Constants.BIZ_ACTIVE_EXCLUDE; import static com.alipay.sofa.ark.spi.constant.Constants.BIZ_ACTIVE_INCLUDE; import static com.alipay.sofa.ark.spi.constant.Constants.PLUGIN_ACTIVE_EXCLUDE; import static com.alipay.sofa.ark.spi.constant.Constants.PLUGIN_ACTIVE_INCLUDE; /** * @author qilong.zql * @since 0.6.0 */ public class HandleArchiveTest { @Test public void testIncludeExcludePlugin() { try { HandleArchiveStage handleArchiveStage = new HandleArchiveStage(); System.setProperty(PLUGIN_ACTIVE_INCLUDE, "pluginA"); System.setProperty(PLUGIN_ACTIVE_EXCLUDE, "pluginA,pluginB"); PluginModel pluginModel = new PluginModel(); pluginModel.setPluginName("pluginA"); Assert.assertFalse(handleArchiveStage.isPluginExcluded(pluginModel)); pluginModel.setPluginName("pluginB"); Assert.assertTrue(handleArchiveStage.isPluginExcluded(pluginModel)); pluginModel.setPluginName("pluginC"); Assert.assertTrue(handleArchiveStage.isPluginExcluded(pluginModel)); } finally { System.clearProperty(PLUGIN_ACTIVE_EXCLUDE); System.clearProperty(PLUGIN_ACTIVE_INCLUDE); } } @Test public void testIncludePlugin() { try { HandleArchiveStage handleArchiveStage = new HandleArchiveStage(); System.setProperty(PLUGIN_ACTIVE_INCLUDE, "pluginA"); PluginModel pluginModel = new PluginModel(); pluginModel.setPluginName("pluginA"); Assert.assertFalse(handleArchiveStage.isPluginExcluded(pluginModel)); pluginModel.setPluginName("pluginB"); Assert.assertTrue(handleArchiveStage.isPluginExcluded(pluginModel)); pluginModel.setPluginName("pluginC"); Assert.assertTrue(handleArchiveStage.isPluginExcluded(pluginModel)); } finally { System.clearProperty(PLUGIN_ACTIVE_INCLUDE); } } @Test public void testExcludePlugin() { try { HandleArchiveStage handleArchiveStage = new HandleArchiveStage(); System.setProperty(PLUGIN_ACTIVE_EXCLUDE, "pluginA,pluginB"); PluginModel pluginModel = new PluginModel(); pluginModel.setPluginName("pluginA"); Assert.assertTrue(handleArchiveStage.isPluginExcluded(pluginModel)); pluginModel.setPluginName("pluginB"); Assert.assertTrue(handleArchiveStage.isPluginExcluded(pluginModel)); pluginModel.setPluginName("pluginC"); Assert.assertFalse(handleArchiveStage.isPluginExcluded(pluginModel)); } finally { System.clearProperty(PLUGIN_ACTIVE_EXCLUDE); } } @Test public void testNoIncludeExcludeBiz() { HandleArchiveStage handleArchiveStage = new HandleArchiveStage(); PluginModel pluginModel = new PluginModel(); pluginModel.setPluginName("pluginA"); Assert.assertFalse(handleArchiveStage.isPluginExcluded(pluginModel)); pluginModel.setPluginName("pluginB"); Assert.assertFalse(handleArchiveStage.isPluginExcluded(pluginModel)); } @Test public void testIncludeExcludeBiz() { try { HandleArchiveStage handleArchiveStage = new HandleArchiveStage(); System.setProperty(BIZ_ACTIVE_INCLUDE, "bizA:1.0.0"); System.setProperty(BIZ_ACTIVE_EXCLUDE, "bizA:1.0.0,BizB:1.0.0"); BizModel bizModel = new BizModel(); bizModel.setBizName("bizA").setBizVersion("1.0.0"); Assert.assertFalse(handleArchiveStage.isBizExcluded(bizModel)); bizModel.setBizName("bizB"); Assert.assertTrue(handleArchiveStage.isBizExcluded(bizModel)); bizModel.setBizName("bizC"); Assert.assertTrue(handleArchiveStage.isBizExcluded(bizModel)); } finally { System.clearProperty(BIZ_ACTIVE_INCLUDE); System.clearProperty(BIZ_ACTIVE_EXCLUDE); } } @Test public void testIncludeBiz() { try { HandleArchiveStage handleArchiveStage = new HandleArchiveStage(); System.setProperty(BIZ_ACTIVE_INCLUDE, "bizA:1.0.0"); BizModel bizModel = new BizModel(); bizModel.setBizName("bizA").setBizVersion("1.0.0"); Assert.assertFalse(handleArchiveStage.isBizExcluded(bizModel)); bizModel.setBizName("pluginB"); Assert.assertTrue(handleArchiveStage.isBizExcluded(bizModel)); bizModel.setBizName("pluginC"); Assert.assertTrue(handleArchiveStage.isBizExcluded(bizModel)); } finally { System.clearProperty(BIZ_ACTIVE_INCLUDE); } } @Test public void testExcludeBiz() { try { HandleArchiveStage handleArchiveStage = new HandleArchiveStage(); System.setProperty(BIZ_ACTIVE_EXCLUDE, "bizA:1.0.0,bizB:1.0.0"); BizModel bizModel = new BizModel(); bizModel.setBizName("bizA").setBizVersion("1.0.0"); Assert.assertTrue(handleArchiveStage.isBizExcluded(bizModel)); bizModel.setBizName("bizB"); Assert.assertTrue(handleArchiveStage.isBizExcluded(bizModel)); bizModel.setBizName("bizC"); Assert.assertFalse(handleArchiveStage.isBizExcluded(bizModel)); } finally { System.clearProperty(BIZ_ACTIVE_EXCLUDE); } } @Test public void testNoIncludeExcludePlugin() { HandleArchiveStage handleArchiveStage = new HandleArchiveStage(); BizModel bizModel = new BizModel(); bizModel.setBizName("bizA").setBizVersion("1.0.0"); Assert.assertFalse(handleArchiveStage.isBizExcluded(bizModel)); bizModel.setBizName("bizB"); Assert.assertFalse(handleArchiveStage.isBizExcluded(bizModel)); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/plugin/PluginCommandProviderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.plugin; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.container.model.PluginModel; import com.alipay.sofa.ark.container.service.plugin.PluginCommandProvider.PluginCommand; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import org.junit.Test; import java.lang.reflect.Field; import java.net.URL; import java.util.HashSet; import java.util.Set; import static com.google.common.collect.Sets.newHashSet; import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author qilong.zql * @since 0.6.0 */ public class PluginCommandProviderTest { @Test public void testPluginCommandFormat() { PluginCommandProvider pluginCommandProvider = new PluginCommandProvider(); assertFalse(pluginCommandProvider.validate(" plugin ")); assertTrue(pluginCommandProvider.validate(" plugin -h ")); assertTrue(pluginCommandProvider.validate(" plugin -m pluginA ")); assertTrue(pluginCommandProvider.validate(" plugin -s pluginB pluginA ")); assertTrue(pluginCommandProvider.validate(" plugin -d plugin* ")); assertTrue(pluginCommandProvider.validate(" plugin -m -d -s plugin* ")); assertTrue(pluginCommandProvider.validate(" plugin -msd pluginA ")); assertFalse(pluginCommandProvider.validate(" plu")); assertFalse(pluginCommandProvider.validate(" plugin -h pluginA ")); assertFalse(pluginCommandProvider.validate(" plugin -hm pluginA ")); assertFalse(pluginCommandProvider.validate(" plugin -mb pluginA ")); assertFalse(pluginCommandProvider.validate(" plugin -m -b pluginA ")); assertFalse(pluginCommandProvider.validate(" plugin -m ")); } @Test public void testPluginCommandProcess() { URL[] urls = ClassLoaderUtils.getURLs(this.getClass().getClassLoader()); final Plugin pluginA = new PluginModel().setPluginName("pluginA") .setImportClasses("com.google.common.io.AppendableWriter") .setImportPackages(" com.google.common.cache, com.google.common.base ") .setImportResources(" import_resource1").setExportPackages("com.alipay.sofa.*") .setExportResources(" export_resource1, export_resource2") .setPluginActivator("com.alipay.sofa.pluginA.Activator").setClassPath(urls) .setGroupId("com.alipay.sofa").setArtifactId("ark-mock-pluginA").setVersion("1.0.0"); final Plugin pluginB = new PluginModel().setPluginName("pluginB") .setImportClasses("com.google.common.io.AppendableWriter") .setImportPackages(" com.google.common.cache, com.google.common.base ") .setImportResources(" import_resource1").setExportPackages("com.alipay.sofa.*") .setExportResources(" export_resource1, export_resource2") .setPluginActivator("com.alipay.sofa.pluginA.Activator").setClassPath(urls) .setGroupId("com.alipay.sofa").setArtifactId("ark-mock-pluginB").setVersion("1.0.0"); final Set set = new HashSet<>(); set.add("pluginA"); set.add("pluginB"); PluginManagerService pluginManagerService = mock(PluginManagerService.class); when(pluginManagerService.getAllPluginNames()).thenReturn(set); when(pluginManagerService.getPluginByName("pluginA")).thenReturn(pluginA); when(pluginManagerService.getPluginByName("pluginB")).thenReturn(pluginB); PluginCommandProvider pluginCommandProvider = new PluginCommandProvider(); try { Field field = PluginCommandProvider.class.getDeclaredField("pluginManagerService"); field.setAccessible(true); field.set(pluginCommandProvider, pluginManagerService); } catch (Throwable throwable) { // ignore } String errorMessage = "Error command format. Pls type 'plugin -h' to get help message\n"; assertTrue(errorMessage.equals(pluginCommandProvider.handleCommand("plu"))); assertTrue(errorMessage.equals(pluginCommandProvider.handleCommand("plugin -h pluginA"))); assertTrue(errorMessage.equals(pluginCommandProvider.handleCommand("plugin -b pluginA"))); assertTrue(errorMessage.equals(pluginCommandProvider.handleCommand("plu"))); assertTrue(pluginCommandProvider.getHelp().equals( pluginCommandProvider.handleCommand("plugin -h"))); assertTrue(errorMessage.equals(pluginCommandProvider.handleCommand("plugin "))); String details = pluginCommandProvider.handleCommand("plugin -m -d -s pluginA"); assertTrue(details.contains("Activator")); assertTrue(details.contains("GroupId")); details = pluginCommandProvider.handleCommand("plugin -d pluginB"); assertTrue(details.contains("GroupId")); assertFalse(details.contains("Activator")); details = pluginCommandProvider.handleCommand("plugin -m plugin."); assertTrue(details.contains("pluginA")); assertTrue(details.contains("pluginB")); details = pluginCommandProvider.handleCommand("plugin -m pluginC"); assertTrue(details.contains("no matched plugin candidates.")); } @Test public void testPluginList() { PluginCommandProvider pluginCommandProvider = new PluginCommandProvider(); PluginCommand pluginCommand = pluginCommandProvider.new PluginCommand(null); assertFalse(pluginCommand.isValidate()); pluginCommand = pluginCommandProvider.new PluginCommand("plugin -"); assertFalse(pluginCommand.isValidate()); pluginCommand = pluginCommandProvider.new PluginCommand("plugin -a"); PluginManagerService pluginManagerService = mock(PluginManagerService.class); try { Field field = PluginCommandProvider.class.getDeclaredField("pluginManagerService"); field.setAccessible(true); field.set(pluginCommandProvider, pluginManagerService); } catch (Throwable throwable) { // ignore } assertEquals("no plugins.\nplugin count = 0\n", pluginCommand.process()); when(pluginManagerService.getAllPluginNames()).thenReturn(newHashSet("a")); assertEquals("a\nplugin count = 1\n", pluginCommand.process()); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/plugin/PluginFactoryServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.plugin; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.loader.JarPluginArchive; import com.alipay.sofa.ark.loader.archive.JarFileArchive; import com.alipay.sofa.ark.loader.jar.JarFile; import com.alipay.sofa.ark.spi.archive.PluginArchive; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.service.plugin.PluginFactoryService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import org.junit.Test; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.HashSet; import java.util.Set; import static com.alipay.sofa.ark.api.ArkConfigs.putStringValue; import static com.alipay.sofa.ark.spi.constant.Constants.PLUGIN_EXTENSION_FORMAT; import static java.lang.String.format; import static java.util.Arrays.asList; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author qilong.zql * @since 0.4.0 */ public class PluginFactoryServiceTest extends BaseTest { private PluginFactoryService pluginFactoryService = new PluginFactoryServiceImpl(); @Test public void test() throws Throwable { ClassLoader cl = Thread.currentThread().getContextClassLoader(); URL samplePlugin = cl.getResource("sample-plugin.jar"); Plugin plugin = pluginFactoryService.createPlugin(new File(samplePlugin.getFile())); assertNotNull(plugin); } @Test public void testCreatePluginWithExtensions() throws Throwable { ClassLoader cl = Thread.currentThread().getContextClassLoader(); URL samplePlugin = cl.getResource("sample-plugin.jar"); File file = new File(samplePlugin.getFile()); JarFile pluginFile = new JarFile(file); JarFileArchive jarFileArchive = new JarFileArchive(pluginFile); JarPluginArchive jarPluginArchive = new JarPluginArchive(jarFileArchive); // inject URL[] extensions = new URL[1]; URL sampleBiz = cl.getResource("sample-biz.jar"); JarFile bizFile = new JarFile(new File(sampleBiz.getFile())); extensions[0] = bizFile.getUrl(); // export Set exportPackages = new HashSet<>(); exportPackages.add("com.alipay.test.export.*"); putStringValue(format(PLUGIN_EXTENSION_FORMAT, "sample-ark-plugin"), "tracer-core:3.0.10"); Plugin plugin = pluginFactoryService.createPlugin(jarPluginArchive, extensions, exportPackages); assertNotNull(plugin); assertEquals(plugin.getExportPackages().size(), 2); assertTrue(asList(plugin.getClassPath()).contains(bizFile.getUrl())); } @Test public void testCreateEmbedPlugin() throws IOException { ClassLoader cl = Thread.currentThread().getContextClassLoader(); URL samplePlugin = cl.getResource("sample-plugin.jar"); PluginArchive archive = new JarPluginArchive(new JarFileArchive(new File( samplePlugin.getFile()))); Plugin plugin = pluginFactoryService.createEmbedPlugin(archive, this.getClass() .getClassLoader()); assertNotNull(plugin); } @Test(expected = ArkRuntimeException.class) public void testDeploy() { PluginDeployServiceImpl pluginDeployServiceImpl = new PluginDeployServiceImpl(); PluginManagerService pluginManagerService = mock(PluginManagerService.class); Plugin plugin = mock(Plugin.class); doThrow(new ArkRuntimeException("test")).when(plugin).start(); when(pluginManagerService.getPluginsInOrder()).thenReturn(asList(plugin)); pluginDeployServiceImpl.pluginManagerService = pluginManagerService; pluginDeployServiceImpl.deploy(); } @Test(expected = ArkRuntimeException.class) public void testUndeploy() { PluginDeployServiceImpl pluginDeployServiceImpl = new PluginDeployServiceImpl(); PluginManagerService pluginManagerService = mock(PluginManagerService.class); Plugin plugin = mock(Plugin.class); doThrow(new ArkRuntimeException("test")).when(plugin).stop(); when(pluginManagerService.getPluginsInOrder()).thenReturn(asList(plugin)); pluginDeployServiceImpl.pluginManagerService = pluginManagerService; pluginDeployServiceImpl.unDeploy(); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/plugin/PluginManagerServiceTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.plugin; import com.alipay.sofa.ark.container.model.PluginModel; import com.alipay.sofa.ark.container.service.plugin.PluginManagerServiceImpl; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import org.junit.Assert; import org.junit.Test; import static org.junit.Assert.*; /** * * @author ruoshan * @since 0.1.0 */ public class PluginManagerServiceTest { private PluginManagerService pluginManagerService = new PluginManagerServiceImpl(); @Test public void testGetPluginByName() { PluginModel plugin = new PluginModel(); plugin.setPluginName("plugin A"); pluginManagerService.registerPlugin(plugin); assertNotNull(pluginManagerService.getPluginByName(plugin.getPluginName())); assertNull(pluginManagerService.getPluginByName("test")); } @Test public void testGetAllPluginNames() { PluginModel pluginA = new PluginModel(); pluginA.setPluginName("plugin A"); pluginManagerService.registerPlugin(pluginA); PluginModel pluginB = new PluginModel(); pluginB.setPluginName("plugin B"); pluginManagerService.registerPlugin(pluginB); assertTrue(pluginManagerService.getAllPluginNames().contains(pluginA.getPluginName())); assertTrue(pluginManagerService.getAllPluginNames().contains(pluginB.getPluginName())); assertEquals(2, pluginManagerService.getAllPluginNames().size()); } @Test public void testGetPluginsInOrder() { PluginModel pluginA = new PluginModel(); pluginA.setPluginName("plugin A").setPriority("100"); pluginManagerService.registerPlugin(pluginA); PluginModel pluginB = new PluginModel(); pluginB.setPluginName("plugin B").setPriority("10"); pluginManagerService.registerPlugin(pluginB); PluginModel pluginC = new PluginModel(); pluginC.setPluginName("plugin C").setPriority("1000"); pluginManagerService.registerPlugin(pluginC); assertEquals(3, pluginManagerService.getPluginsInOrder().size()); assertEquals(pluginB, pluginManagerService.getPluginsInOrder().get(0)); assertEquals(pluginA, pluginManagerService.getPluginsInOrder().get(1)); assertEquals(pluginC, pluginManagerService.getPluginsInOrder().get(2)); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/registry/ServiceRegistrationTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.registry; import com.alipay.sofa.ark.common.util.ClassUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.model.PluginContextImpl; import com.alipay.sofa.ark.container.model.PluginModel; import com.alipay.sofa.ark.container.registry.ContainerServiceProvider; import com.alipay.sofa.ark.container.registry.DefaultServiceFilter; import com.alipay.sofa.ark.container.registry.PluginServiceProvider; import com.alipay.sofa.ark.container.registry.ServiceMetadataImpl; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.container.service.classloader.PluginClassLoader; import com.alipay.sofa.ark.container.testdata.ITest; import com.alipay.sofa.ark.container.testdata.activator.PluginActivatorA; import com.alipay.sofa.ark.container.testdata.activator.PluginActivatorADup; import com.alipay.sofa.ark.container.testdata.activator.PluginActivatorB; import com.alipay.sofa.ark.container.testdata.activator.PluginActivatorC; import com.alipay.sofa.ark.container.testdata.impl.TestObjectA; import com.alipay.sofa.ark.container.testdata.impl.TestObjectB; import com.alipay.sofa.ark.container.testdata.impl.TestObjectC; import com.alipay.sofa.ark.spi.model.Plugin; import com.alipay.sofa.ark.spi.registry.ServiceFilter; import com.alipay.sofa.ark.spi.registry.ServiceProvider; import com.alipay.sofa.ark.spi.registry.ServiceProviderType; import com.alipay.sofa.ark.spi.registry.ServiceReference; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderService; import com.alipay.sofa.ark.spi.service.plugin.PluginDeployService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import com.alipay.sofa.ark.spi.service.registry.RegistryService; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.net.URL; import java.util.List; import static com.google.common.collect.Sets.newHashSet; import static org.junit.Assert.*; import static org.mockito.Mockito.mock; /** * @author ruoshan * @since 0.1.0 */ public class ServiceRegistrationTest extends BaseTest { private RegistryService registryService; private PluginManagerService pluginManagerService; private PluginDeployService pluginDeployService; private ClassLoaderService classloaderService; private URL classPathURL = ServiceRegistrationTest.class.getClassLoader() .getResource(""); private static final String INTERFACE_CLASS = ITest.class.getName(); @Before public void before() { super.before(); registryService = ArkServiceContainerHolder.getContainer() .getService(RegistryService.class); pluginManagerService = ArkServiceContainerHolder.getContainer().getService( PluginManagerService.class); pluginDeployService = ArkServiceContainerHolder.getContainer().getService( PluginDeployService.class); classloaderService = ArkServiceContainerHolder.getContainer().getService( ClassLoaderService.class); } @After @SuppressWarnings("unchecked") public void after() { registryService.unPublishServices(new DefaultServiceFilter() .setServiceInterface(ITest.class)); super.after(); } @Test @SuppressWarnings("unchecked") public void testPublishService() { ServiceReference iTestServiceReference = registryService.publishService(ITest.class, new TestObjectA(), new ContainerServiceProvider()); Assert.assertNotNull(iTestServiceReference); assertEquals(TestObjectA.OUTPUT, iTestServiceReference.getService().test()); int c = registryService.unPublishServices(new DefaultServiceFilter().setServiceInterface( ITest.class).setProviderType(ServiceProviderType.ARK_CONTAINER)); assertTrue(c == 1); iTestServiceReference = registryService.referenceService(ITest.class); Assert.assertNull(iTestServiceReference); } @Test public void testReferenceService() { registryService.publishService(ITest.class, new TestObjectA(), new ContainerServiceProvider()); ServiceReference iTestServiceReference = registryService .referenceService(ITest.class); Assert.assertNotNull(iTestServiceReference); assertEquals(TestObjectA.OUTPUT, iTestServiceReference.getService().test()); } @Test @SuppressWarnings("unchecked") public void testPublishDuplicateService() { registryService.publishService(ITest.class, new TestObjectA(), new ContainerServiceProvider()); registryService.publishService(ITest.class, new TestObjectB(), new ContainerServiceProvider()); // 只有第一个服务发布成功 assertEquals(1, registryService.referenceServices(ITest.class).size()); assertEquals(TestObjectA.OUTPUT, registryService.referenceService(ITest.class).getService() .test()); registryService.unPublishServices(new DefaultServiceFilter() .setServiceInterface(ITest.class)); assertEquals(0, registryService.referenceServices(ITest.class).size()); registryService.publishService(ITest.class, new TestObjectA(), "testA", new ContainerServiceProvider()); registryService.publishService(ITest.class, new TestObjectB(), "testB", new ContainerServiceProvider()); assertEquals( 2, registryService.referenceServices( new DefaultServiceFilter().setServiceInterface(ITest.class)).size()); assertEquals(TestObjectA.OUTPUT, registryService.referenceService(ITest.class, "testA") .getService().test()); assertEquals(TestObjectB.OUTPUT, registryService.referenceService(ITest.class, "testB") .getService().test()); int c = registryService.unPublishServices(new DefaultServiceFilter().setUniqueId("testA")); assertTrue(c == 1); c = registryService.unPublishServices(new DefaultServiceFilter().setProviderType( ServiceProviderType.ARK_CONTAINER).setServiceInterface(ITest.class)); assertTrue(c == 1); assertEquals(0, registryService.referenceServices(ITest.class).size()); } @Test public void testPublishDuplicateServiceInPlugin() throws Exception { PluginModel pluginA = new PluginModel(); PluginContextImpl pluginContext = new PluginContextImpl(pluginA); pluginA .setPluginName("plugin A") .setPriority("10") .setClassPath(new URL[] { classPathURL }) .setImportClasses(StringUtils.EMPTY_STRING) .setImportPackages(StringUtils.EMPTY_STRING) .setExportPackages(ClassUtils.getPackageName(INTERFACE_CLASS)) .setExportClasses(StringUtils.EMPTY_STRING) .setImportResources(StringUtils.EMPTY_STRING) .setExportResources(StringUtils.EMPTY_STRING) .setPluginActivator(PluginActivatorADup.class.getName()) .setPluginClassLoader( new PluginClassLoader(pluginA.getPluginName(), pluginA.getClassPath())) .setPluginContext(new PluginContextImpl(pluginA)); pluginManagerService.registerPlugin(pluginA); classloaderService.prepareExportClassAndResourceCache(); pluginDeployService.deploy(); assertEquals( 1, registryService.referenceServices( pluginA.getPluginClassLoader().loadClass(ITest.class.getCanonicalName())).size()); int c = registryService.unPublishServices(new DefaultServiceFilter() .setProviderType(ServiceProviderType.ARK_PLUGIN)); assertEquals(1, c); assertEquals(pluginA, pluginContext.getPlugin()); assertEquals(null, pluginContext.getPlugin("notexists")); assertEquals(PluginClassLoader.class, pluginContext.getClassLoader().getClass()); assertEquals(newHashSet("plugin A"), pluginContext.getPluginNames()); } @Test @SuppressWarnings("unchecked") public void testMultipleService() throws Exception { // 非插件发布的服务,优先级别最低 registryService.publishService(ITest.class, new TestObjectA(), new ContainerServiceProvider()); PluginModel pluginA = new PluginModel(); pluginA .setPluginName("plugin A") .setPriority("10") .setClassPath(new URL[] { classPathURL }) .setImportClasses(StringUtils.EMPTY_STRING) .setImportPackages(StringUtils.EMPTY_STRING) .setExportPackages(ClassUtils.getPackageName(INTERFACE_CLASS)) .setExportClasses("") .setImportResources(StringUtils.EMPTY_STRING) .setExportResources(StringUtils.EMPTY_STRING) .setPluginActivator(PluginActivatorA.class.getName()) .setPluginClassLoader( new PluginClassLoader(pluginA.getPluginName(), pluginA.getClassPath())) .setPluginContext(new PluginContextImpl(pluginA)); PluginModel pluginB = new PluginModel(); pluginB .setPluginName("plugin B") .setPriority("1") .setClassPath(new URL[] { classPathURL }) .setImportClasses(INTERFACE_CLASS) .setImportPackages(StringUtils.EMPTY_STRING) .setExportPackages("") .setExportClasses("") .setImportResources(StringUtils.EMPTY_STRING) .setExportResources(StringUtils.EMPTY_STRING) .setPluginActivator(PluginActivatorB.class.getName()) .setPluginClassLoader( new PluginClassLoader(pluginB.getPluginName(), pluginB.getClassPath())) .setPluginContext(new PluginContextImpl(pluginB)); PluginModel pluginC = new PluginModel(); pluginC .setPluginName("plugin C") .setPriority("100") .setClassPath(new URL[] { classPathURL }) .setImportClasses(INTERFACE_CLASS) .setImportPackages(StringUtils.EMPTY_STRING) .setExportPackages("") .setExportClasses("") .setImportResources(StringUtils.EMPTY_STRING) .setExportResources(StringUtils.EMPTY_STRING) .setPluginActivator(PluginActivatorC.class.getName()) .setPluginClassLoader( new PluginClassLoader(pluginC.getPluginName(), pluginC.getClassPath())) .setPluginContext(new PluginContextImpl(pluginC)); pluginManagerService.registerPlugin(pluginA); pluginManagerService.registerPlugin(pluginB); pluginManagerService.registerPlugin(pluginC); classloaderService.prepareExportClassAndResourceCache(); pluginDeployService.deploy(); Class iTest = pluginA.getPluginClassLoader().loadClass(ITest.class.getCanonicalName()); assertEquals( 3, pluginA.getPluginContext() .referenceServices(new DefaultServiceFilter().setServiceInterface(iTest)).size()); // 应该获取到优先级别比较高的服务 ServiceReference reference = pluginC.getPluginContext().referenceService(iTest); PluginServiceProvider provider = (PluginServiceProvider) reference.getServiceMetadata() .getServiceProvider(); assertEquals(pluginB.getPluginName(), provider.getPluginName()); List references = pluginC.getPluginContext().referenceServices( new DefaultServiceFilter().setServiceInterface(iTest)); provider = (PluginServiceProvider) references.get(0).getServiceMetadata() .getServiceProvider(); assertEquals(pluginB.getPluginName(), provider.getPluginName()); } @Test @SuppressWarnings("unchecked") public void testFilter() { final PluginModel pluginA = new PluginModel(); pluginA.setPluginName("plugin A").setPriority("10"); PluginModel pluginB = new PluginModel(); pluginB.setPluginName("plugin B").setPriority("1"); pluginManagerService.registerPlugin(pluginA); pluginManagerService.registerPlugin(pluginB); registryService.publishService(ITest.class, new TestObjectA(), new PluginServiceProvider( pluginA)); registryService.publishService(ITest.class, new TestObjectB(), new PluginServiceProvider( pluginB)); registryService.publishService(ITest.class, new TestObjectC(), new ContainerServiceProvider()); List references = registryService .referenceServices(new DefaultServiceFilter().setServiceInterface(ITest.class) .setProviderType(ServiceProviderType.ARK_PLUGIN)); assertTrue(2 == references.size()); PluginServiceProvider provider = (PluginServiceProvider) references.get(0) .getServiceMetadata().getServiceProvider(); assertEquals(pluginB.getPluginName(), provider.getPluginName()); references = registryService.referenceServices(new ServiceFilter() { @Override public boolean match(ServiceReference serviceReference) { ServiceProvider serviceProvider = serviceReference.getServiceMetadata() .getServiceProvider(); if (serviceProvider instanceof PluginServiceProvider) { if (((PluginServiceProvider) serviceProvider).getPluginName().equals( pluginA.getPluginName())) { return true; } } return false; } }); assertTrue(1 == references.size()); provider = (PluginServiceProvider) references.get(0).getServiceMetadata() .getServiceProvider(); assertEquals(pluginA.getPluginName(), provider.getPluginName()); references = registryService.referenceServices(new DefaultServiceFilter() .setServiceInterface(ITest.class)); assertTrue(3 == references.size()); references = registryService.referenceServices(new DefaultServiceFilter().setProviderType( ServiceProviderType.ARK_CONTAINER).setServiceInterface(ITest.class)); assertTrue(1 == references.size()); assertEquals("TestObject C", ((TestObjectC) references.get(0).getService()).test()); } @Test public void testContainerService() { registryService.publishService(ITest.class, new TestObjectA(), new ContainerServiceProvider(20000)); registryService.publishService(ITest.class, new TestObjectB(), new ContainerServiceProvider(100)); registryService.publishService(ITest.class, new TestObjectC(), new ContainerServiceProvider(200)); assertEquals(TestObjectB.OUTPUT, registryService.referenceService(ITest.class).getService() .test()); assertEquals(3, registryService.referenceServices(ITest.class).size()); } @Test public void testEqualsHashCode() { ContainerServiceProvider containerServiceProvider = new ContainerServiceProvider(); containerServiceProvider.hashCode(); assertEquals(containerServiceProvider, containerServiceProvider); ServiceMetadataImpl serviceMetadataImpl = new ServiceMetadataImpl(this.getClass(), "a", containerServiceProvider); serviceMetadataImpl.hashCode(); assertEquals(serviceMetadataImpl, serviceMetadataImpl); assertFalse(serviceMetadataImpl.equals(null)); PluginServiceProvider pluginServiceProvider = new PluginServiceProvider(mock(Plugin.class)); pluginServiceProvider.hashCode(); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/retrieval/ClassInfoMethodTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.retrieval; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.session.handler.ArkCommandHandler; import com.alipay.sofa.ark.spi.service.injection.InjectionService; import org.junit.Assert; import org.junit.Test; /** * @author yanzhu * @date 2023/10/17 17:55 */ public class ClassInfoMethodTest extends BaseTest { @Test public void testCreateClassInfo() { String classInfo = ClassInfoMethod.createClassInfo(ArkCommandHandler.class, "A1:V1"); Assert.assertTrue(classInfo.contains("class-info")); Assert.assertTrue(classInfo.contains("code-source")); Assert.assertTrue(classInfo.contains("isInterface")); Assert.assertTrue(classInfo.contains("isAnnotation")); Assert.assertTrue(classInfo.contains("isEnum")); Assert.assertTrue(classInfo.contains("container-name")); Assert.assertTrue(classInfo.contains("simple-name")); Assert.assertTrue(classInfo.contains("modifier")); Assert.assertTrue(classInfo.contains("super-class")); Assert.assertTrue(classInfo.contains("class-loader")); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/retrieval/InfoQueryCommandProviderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.retrieval; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.session.handler.ArkCommandHandler; import com.alipay.sofa.ark.spi.service.injection.InjectionService; import org.junit.Assert; import org.junit.Test; /** * @author yanzhu * @date 2023/10/17 17:55 */ public class InfoQueryCommandProviderTest extends BaseTest { private InjectionService injectionService; private InfoQueryCommandProvider queryCommandProvider; @Override public void before() { super.before(); injectionService = arkServiceContainer.getService(InjectionService.class); queryCommandProvider = new InfoQueryCommandProvider(); injectionService.inject(queryCommandProvider); // trigger telnet command thread pool to be created new ArkCommandHandler(); } @Test public void testInfoQueryCommandPattern() { Assert.assertFalse(queryCommandProvider.validate("ck")); Assert.assertTrue(queryCommandProvider.validate("ck -h")); Assert.assertFalse(queryCommandProvider.validate("ck -c")); Assert.assertFalse(queryCommandProvider.validate("ck -ch")); Assert.assertTrue(queryCommandProvider.validate("ck -c com.example.HelloWorld")); } @Test public void testClassInfo() { String classInfo = queryCommandProvider .handleCommand("ck -c com.alipay.sofa.ark.container.session.handler.ArkCommandHandler"); Assert.assertTrue(classInfo.contains("class-info")); Assert.assertTrue(classInfo.contains("code-source")); Assert.assertTrue(classInfo.contains("container-name")); Assert.assertTrue(classInfo.contains("class-loader")); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/retrieval/ViewRenderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.retrieval; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.session.handler.ArkCommandHandler; import org.junit.Assert; import org.junit.Test; /** * @author yanzhu * @date 2023/10/17 17:55 */ public class ViewRenderTest extends BaseTest { @Test public void testRenderClassInfo() { ClassInfoVO classInfo = new ClassInfoVO(); classInfo.setClassInfo("com.example.HelloWorld"); classInfo.setCodeSource("/home/admin"); classInfo.setInterface(false); classInfo.setAnnotation(false); classInfo.setEnum(false); classInfo.setContainerName("A1:V1"); classInfo.setSimpleName("helloWorld"); classInfo.setModifier("public"); classInfo.setSuperClass(new String[] { "com.example" }); classInfo.setClassloader(new String[] { "com.example.ClassLoader" }); String renderClassInfo = ViewRender.renderClassInfo(classInfo); Assert.assertTrue(renderClassInfo.contains("class-info")); Assert.assertTrue(renderClassInfo.contains("code-source")); Assert.assertTrue(renderClassInfo.contains("isInterface")); Assert.assertTrue(renderClassInfo.contains("isAnnotation")); Assert.assertTrue(renderClassInfo.contains("isEnum")); Assert.assertTrue(renderClassInfo.contains("container-name")); Assert.assertTrue(renderClassInfo.contains("simple-name")); Assert.assertTrue(renderClassInfo.contains("modifier")); Assert.assertTrue(renderClassInfo.contains("super-class")); Assert.assertTrue(renderClassInfo.contains("class-loader")); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/session/CommandHandlerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.service.session; import com.alipay.sofa.ark.container.BaseTest; import com.alipay.sofa.ark.container.registry.ContainerServiceProvider; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.container.session.handler.ArkCommandHandler; import com.alipay.sofa.ark.spi.service.registry.RegistryService; import com.alipay.sofa.ark.spi.service.session.CommandProvider; import org.junit.Assert; import org.junit.Test; /** * @author qilong.zql * @since 0.4.0 */ public class CommandHandlerTest extends BaseTest { @Test public void test() { ArkCommandHandler arkCommandHandler = new ArkCommandHandler(); RegistryService registryService = ArkServiceContainerHolder.getContainer().getService( RegistryService.class); registryService.publishService(CommandProvider.class, new MockCommandProvider(), new ContainerServiceProvider()); Assert.assertTrue(arkCommandHandler.handleCommand("any").contains("mock help")); Assert.assertTrue("mock command provider".equals(arkCommandHandler.handleCommand("mock"))); } public class MockCommandProvider implements CommandProvider { @Override public String getHelp() { return "mock help"; } @Override public boolean validate(String command) { return command.contains("mock"); } @Override public String handleCommand(String command) { return "mock command provider"; } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/session/NettyTelnetServerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.session; import com.alipay.sofa.ark.container.service.ArkServiceContainer; import com.alipay.sofa.ark.container.session.NettyTelnetServer.NettyTelnetHandler; import com.alipay.sofa.ark.container.session.NettyTelnetServer.NettyTelnetInitializer; import com.alipay.sofa.ark.exception.ArkRuntimeException; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.net.InetSocketAddress; import static com.alipay.sofa.ark.common.util.EnvironmentUtils.getProperty; import static com.alipay.sofa.ark.common.util.EnvironmentUtils.setProperty; import static com.alipay.sofa.ark.container.service.ArkServiceContainerHolder.getContainer; import static com.alipay.sofa.ark.container.service.ArkServiceContainerHolder.setContainer; import static com.alipay.sofa.ark.spi.constant.Constants.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; public class NettyTelnetServerTest { private ArkServiceContainer originalArkServiceContainer; private String originalSecurityEnable; private String originalTelnetPort; @Before public void before() { originalArkServiceContainer = getContainer(); originalSecurityEnable = getProperty(TELNET_SERVER_SECURITY_ENABLE); originalTelnetPort = getProperty(TELNET_PORT_ATTRIBUTE); } @After public void after() { setContainer(originalArkServiceContainer); setProperty(TELNET_SERVER_SECURITY_ENABLE, originalSecurityEnable != null ? originalSecurityEnable : ""); setProperty(TELNET_PORT_ATTRIBUTE, originalTelnetPort != null ? originalTelnetPort : ""); } @Test public void testNettyTelnetInitializer() throws Exception { SocketChannel socketChannel = mock(SocketChannel.class); ChannelPipeline pipeline = mock(ChannelPipeline.class); when(socketChannel.pipeline()).thenReturn(pipeline); InetSocketAddress inetSocketAddress = mock(InetSocketAddress.class); when(socketChannel.remoteAddress()).thenReturn(inetSocketAddress); when(inetSocketAddress.getHostName()).thenReturn(LOCAL_HOST + "1"); setContainer(mock(ArkServiceContainer.class)); NettyTelnetInitializer nettyTelnetInitializer = new NettyTelnetInitializer(); nettyTelnetInitializer.initChannel(socketChannel); setProperty(TELNET_SERVER_SECURITY_ENABLE, "true"); nettyTelnetInitializer.initChannel(socketChannel); verify(pipeline, times(4)).addLast(any()); } @Test public void testNettyTelnetHandler() throws Exception { setContainer(mock(ArkServiceContainer.class)); NettyTelnetHandler nettyTelnetHandler = new NettyTelnetHandler(); ChannelHandlerContext context = mock(ChannelHandlerContext.class); when(context.channel()).thenReturn(mock(Channel.class)); nettyTelnetHandler.channelActive(context); nettyTelnetHandler.exceptionCaught(context, new Exception()); nettyTelnetHandler.channelRead0(context, ""); nettyTelnetHandler.channelRead0(context, "q"); } @Test(expected = ArkRuntimeException.class) public void testStandardTelnetServerImplWithInvalidNumber() { setProperty(TELNET_PORT_ATTRIBUTE, "a"); new StandardTelnetServerImpl(); } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/test/TestHelperTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.test; import com.alipay.sofa.ark.container.ArkContainer; import com.alipay.sofa.ark.container.ArkContainerTest; import org.junit.Test; import java.net.URL; import static com.alipay.sofa.ark.container.ArkContainer.main; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class TestHelperTest { private URL jarURL = ArkContainerTest.class.getClassLoader().getResource("test.jar"); @Test public void testCreateNoneDelegateTestClassLoader() { ArkContainer arkContainer = null; try { String[] args = new String[] { "-Ajar=" + jarURL.toExternalForm(), "-Aclasspath=" + jarURL.toString() }; arkContainer = (ArkContainer) main(args); TestHelper testHelper = new TestHelper(arkContainer); assertTrue(testHelper.isStarted()); assertEquals(NoneDelegateTestClassLoader.class, testHelper .createNoneDelegateTestClassLoader().getClass()); } finally { if (arkContainer != null) { arkContainer.stop(); } } } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/testdata/ITest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.testdata; /** * @author ruoshan * @since 0.1.0 */ public interface ITest { /** * a simple facade * @return */ String test(); } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/testdata/activator/PluginActivatorA.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.testdata.activator; import com.alipay.sofa.ark.container.testdata.ITest; import com.alipay.sofa.ark.container.testdata.impl.TestObjectA; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.model.PluginContext; import com.alipay.sofa.ark.spi.service.PluginActivator; /** * * @author ruoshan * @since 0.1.0 */ public class PluginActivatorA implements PluginActivator { @Override public void start(PluginContext context) throws ArkRuntimeException { context.publishService(ITest.class, new TestObjectA()); } @Override public void stop(PluginContext context) throws ArkRuntimeException { } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/testdata/activator/PluginActivatorADup.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.testdata.activator; import com.alipay.sofa.ark.container.testdata.ITest; import com.alipay.sofa.ark.container.testdata.impl.TestObjectA; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.model.PluginContext; import com.alipay.sofa.ark.spi.service.PluginActivator; /** * * @author ruoshan * @since 0.1.0 */ public class PluginActivatorADup implements PluginActivator { @Override public void start(PluginContext context) throws ArkRuntimeException { context.publishService(ITest.class, new TestObjectA()); context.publishService(ITest.class, new TestObjectA()); } @Override public void stop(PluginContext context) throws ArkRuntimeException { } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/testdata/activator/PluginActivatorB.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.testdata.activator; import com.alipay.sofa.ark.container.testdata.ITest; import com.alipay.sofa.ark.container.testdata.impl.TestObjectB; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.model.PluginContext; import com.alipay.sofa.ark.spi.service.PluginActivator; /** * * @author ruoshan * @since 0.1.0 */ public class PluginActivatorB implements PluginActivator { @Override public void start(PluginContext context) throws ArkRuntimeException { context.publishService(ITest.class, new TestObjectB()); } @Override public void stop(PluginContext context) throws ArkRuntimeException { } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/testdata/activator/PluginActivatorC.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.testdata.activator; import com.alipay.sofa.ark.container.testdata.ITest; import com.alipay.sofa.ark.container.testdata.impl.TestObjectC; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.model.PluginContext; import com.alipay.sofa.ark.spi.service.PluginActivator; /** * * @author ruoshan * @since 0.1.0 */ public class PluginActivatorC implements PluginActivator { @Override public void start(PluginContext context) throws ArkRuntimeException { context.publishService(ITest.class, new TestObjectC()); } @Override public void stop(PluginContext context) throws ArkRuntimeException { } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/testdata/classloader/ClassLoaderTestClass.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.testdata.classloader; /** * * @author ruoshan * @since 0.5.0 */ public class ClassLoaderTestClass { } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/testdata/impl/TestObjectA.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.testdata.impl; import com.alipay.sofa.ark.container.testdata.ITest; /** * * @author ruoshan * @since 0.1.0 */ public class TestObjectA implements ITest { public static final String OUTPUT = "TestObject A"; @Override public String test() { return OUTPUT; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/testdata/impl/TestObjectB.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.testdata.impl; import com.alipay.sofa.ark.container.testdata.ITest; /** * * @author ruoshan * @since 0.1.0 */ public class TestObjectB implements ITest { public static final String OUTPUT = "TestObject B"; @Override public String test() { return OUTPUT; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/testdata/impl/TestObjectC.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.container.testdata.impl; import com.alipay.sofa.ark.container.testdata.ITest; /** * * @author ruoshan * @since 0.1.0 */ public class TestObjectC implements ITest { @Override public String test() { return "TestObject C"; } } ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/resources/META-INF/services/sofa-ark/com.alipay.sofa.ark.container.service.extension.spi.ServiceB ================================================ com.alipay.sofa.ark.container.service.extension.spi.impl.ServiceBImpl1 com.alipay.sofa.ark.container.service.extension.spi.impl.ServiceBImpl2 com.alipay.sofa.ark.container.service.extension.spi.impl.ServiceBImpl3 com.alipay.sofa.ark.container.service.extension.spi.impl.ServiceBImpl4 ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/resources/META-INF/services/sofa-ark/com.alipay.sofa.ark.spi.service.biz.AddBizToStaticDeployHook ================================================ com.alipay.sofa.ark.container.service.biz.hook.TestAddBizToStaticDeployHook ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/resources/META-INF/services/sofa-ark/com.alipay.sofa.ark.spi.service.classloader.ClassLoaderHook ================================================ com.alipay.sofa.ark.container.service.classloader.hook.TestBizClassLoaderHook com.alipay.sofa.ark.container.service.classloader.hook.TestPluginClassLoaderHook ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/resources/META-INF/services/sofa-ark/serviceA ================================================ com.alipay.sofa.ark.container.service.extension.spi.SingletonService ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/resources/META-INF/services/sofa-ark/serviceD ================================================ com.alipay.sofa.ark.container.service.extension.spi.impl.ServiceDImpl ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/resources/export/folderA/test1.xml ================================================ ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/resources/export/folderA/test2.xml ================================================ ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/resources/export/folderB/test3.xml ================================================ ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/resources/export/folderB/test4.xml ================================================ ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/resources/multi_export.xml ================================================ ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/resources/pluginA_export_resource1.xml ================================================ ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/resources/pluginA_export_resource2.xml ================================================ ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/resources/pluginA_not_export_resource.xml ================================================ ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/resources/profile-test.jar ================================================ [File too large to display: 13.4 MB] ================================================ FILE: sofa-ark-parent/core-impl/container/src/test/resources/static-combine-springboot-executable.jar ================================================ [File too large to display: 43.6 MB] ================================================ FILE: sofa-ark-parent/core-impl/pom.xml ================================================ 4.0.0 sofa-ark-parent com.alipay.sofa ${sofa.ark.version} sofa-ark-core-impl ${project.groupId}:${project.artifactId} pom container archive ================================================ FILE: sofa-ark-parent/pom.xml ================================================ sofa-ark-bom com.alipay.sofa ${sofa.ark.version} ../sofa-ark-bom 4.0.0 sofa-ark-parent ${project.groupId}:${project.artifactId} pom assembly core core-impl support ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/README.md ================================================ # Ark Gradle打包插件使用 `sofa-ark-gradle-plugin`模块是Ark打包工具的Gradle版本实现,和Maven打包工具`sofa-ark-maven-plugin`有同样的功能,用于打包ark包和biz包。 # 配置 `sofa-ark-gradle-plugin` 使用 arkConfig 来进行配置。 # 如何使用 1. 本地发布引用 2. 远程仓库引入(待申请) 参考`sofa-ark-plugin-gradle-plugin`的本地发布和引入。 使用Gradle刷新后,如果一切正常,会在IDEA右侧Gradle任务列表中出现arkJar,具体如下: Tasks > build > arkJar,点击arkJar执行,会在指定的outputDirectory中输出ark包和biz包。 ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/build.gradle ================================================ plugins { id 'java' id 'java-gradle-plugin' id 'maven-publish' } ext { arkGradlePluginId = 'sofa-ark-gradle-plugin' } group = 'com.alipay.sofa' version = '1.0.0' sourceCompatibility = '1.8' publishing { publications { maven(MavenPublication) { from components.java groupId = project.group artifactId = arkGradlePluginId version = project.version } } repositories { maven { mavenLocal() } } } gradlePlugin { plugins { DependenciesPlugin{ id = arkGradlePluginId implementationClass = 'com.alipay.sofa.ark.plugin.SofaArkGradlePlugin' } } } repositories { mavenLocal() mavenCentral() } dependencies { implementation 'org.ow2.asm:asm:9.4' implementation 'org.apache.commons:commons-compress:1.26.1' implementation 'com.alipay.sofa:sofa-ark-tools:2.2.12' } test { useJUnitPlatform() } ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/settings.gradle ================================================ rootProject.name = 'ark-gradle-plugin' ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/src/main/java/com/alipay/sofa/ark/plugin/ArkArchiveSupport.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.function.Function; import java.util.function.Supplier; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.gradle.api.GradleException; import org.gradle.api.file.CopySpec; import org.gradle.api.file.FileCopyDetails; import org.gradle.api.file.FileTreeElement; import org.gradle.api.file.RelativePath; import org.gradle.api.internal.file.copy.CopyAction; import org.gradle.api.internal.file.copy.CopyActionProcessingStream; import org.gradle.api.internal.file.copy.FileCopyDetailsInternal; import org.gradle.api.java.archives.Attributes; import org.gradle.api.java.archives.Manifest; import org.gradle.api.provider.Property; import org.gradle.api.specs.Spec; import org.gradle.api.specs.Specs; import org.gradle.api.tasks.WorkResult; import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.util.PatternSet; import org.gradle.util.GradleVersion; import com.alipay.sofa.ark.tools.git.GitInfo; import com.alipay.sofa.ark.tools.git.JGitParser; public class ArkArchiveSupport { private static final byte[] ZIP_FILE_HEADER = new byte[] { 'P', 'K', 3, 4 }; private static final String BIZ_MARKER = "com/alipay/sofa/ark/biz/mark"; private static final String PLUGIN_MARKER = "com/alipay/sofa/ark/plugin/mark"; private static final String CONTAINER_MARK = "com/alipay/sofa/ark/container/mark"; private static final Set DEFAULT_LAUNCHER_CLASSES; static { Set defaultLauncherClasses = new HashSet<>(); defaultLauncherClasses.add("org.springframework.boot.loader.JarLauncher"); defaultLauncherClasses.add("org.springframework.boot.loader.PropertiesLauncher"); defaultLauncherClasses.add("org.springframework.boot.loader.WarLauncher"); DEFAULT_LAUNCHER_CLASSES = Collections.unmodifiableSet(defaultLauncherClasses); } private final PatternSet requiresUnpack = new PatternSet(); private final PatternSet exclusions = new PatternSet(); private final String loaderMainClass; private final Spec librarySpec; private final Function compressionResolver; private final String arkVersion; private SofaArkGradlePluginExtension arkExtension; private final GitInfo gitInfo; private java.util.jar.Manifest arkManifest = new java.util.jar.Manifest(); private final List pluginFiles = new ArrayList<>(); private final List bizFiles = new ArrayList<>(); private List conFile = new ArrayList<>(); public ArkArchiveSupport(String loaderMainClass, Spec librarySpec, Function compressionResolver, File gitDic, SofaArkGradlePluginExtension arkExtension) { this.loaderMainClass = loaderMainClass; this.librarySpec = librarySpec; this.compressionResolver = compressionResolver; this.requiresUnpack.include(Specs.satisfyNone()); // TODO: configure as the version of sofa-ark this.arkVersion = "2.2.14"; this.arkExtension = arkExtension; this.gitInfo = JGitParser.parse(gitDic); buildArkManifest(); } public void configureBizManifest(Manifest manifest, String mainClass, String classes, String lib, String classPathIndex) { Attributes attributes = manifest.getAttributes(); attributes.putIfAbsent("Start-Class", mainClass); attributes.putIfAbsent("Main-Class", mainClass); attributes.putIfAbsent("Spring-Boot-Classes", classes); buildModuleManifest(manifest); } public void buildArkManifest(){ this.arkManifest.getMainAttributes().putValue("Manifest-Version", "1.0"); this.arkManifest.getMainAttributes().putValue("Main-Class", this.loaderMainClass); this.arkManifest.getMainAttributes().putValue("Start-Class", this.loaderMainClass); this.arkManifest.getMainAttributes().putValue("Sofa-Ark-Version",this.arkVersion); this.arkManifest.getMainAttributes().putValue("Ark-Container-Root","SOFA-ARK/container/"); this.arkManifest.getMainAttributes().putValue("build-time", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").format(new Date())); if (gitInfo != null) { this.arkManifest.getMainAttributes().putValue("remote-origin-url", gitInfo.getRepository()); this.arkManifest.getMainAttributes().putValue("commit-branch", gitInfo.getBranchName()); this.arkManifest.getMainAttributes().putValue("commit-id", gitInfo.getLastCommitId()); this.arkManifest.getMainAttributes().putValue("commit-user-name", gitInfo.getLastCommitUser()); this.arkManifest.getMainAttributes() .putValue("commit-user-email", gitInfo.getLastCommitEmail()); this.arkManifest.getMainAttributes().putValue("COMMIT_TIME", gitInfo.getLastCommitDateTime()); this.arkManifest.getMainAttributes().putValue("COMMIT_TIMESTAMP", String.valueOf(gitInfo.getLastCommitTime())); this.arkManifest.getMainAttributes().putValue("build-user", gitInfo.getBuildUser()); this.arkManifest.getMainAttributes().putValue("build-email", gitInfo.getBuildEmail()); } } private void buildModuleManifest(Manifest manifest){ Attributes attributes = manifest.getAttributes(); attributes.putIfAbsent("Ark-Biz-Name",this.arkExtension.getBizName().get()); attributes.putIfAbsent("Ark-Biz-Version",this.arkExtension.getBizVersion().get()); attributes.putIfAbsent("priority",this.arkExtension.getPriority().get()); attributes.putIfAbsent("web-context-path", this.arkExtension.getWebContextPath().get()); attributes.putIfAbsent("deny-import-packages",joinSet(this.arkExtension.getDenyImportPackages().get())); attributes.putIfAbsent("deny-import-classes",joinSet(this.arkExtension.getDenyImportClasses().get())); attributes.putIfAbsent("deny-import-resources",joinSet(this.arkExtension.getDenyImportResources().get())); attributes.putIfAbsent("inject-plugin-dependencies", joinSet(this.arkExtension.getInjectPluginDependencies().get())); attributes.putIfAbsent("inject-export-packages",joinSet(this.arkExtension.getInjectPluginExportPackages().get())); appendBuildInfo(manifest); } private String joinSet(Set set) { return set != null ? String.join(",", set) : ""; } private void appendBuildInfo(Manifest manifest) { Attributes attributes = manifest.getAttributes(); attributes.putIfAbsent("build-time", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").format(new Date())); if (gitInfo != null) { attributes.putIfAbsent("remote-origin-url", gitInfo.getRepository()); attributes.putIfAbsent("commit-branch", gitInfo.getBranchName()); attributes.putIfAbsent("commit-id", gitInfo.getLastCommitId()); attributes.putIfAbsent("commit-user-name", gitInfo.getLastCommitUser()); attributes.putIfAbsent("commit-user-email", gitInfo.getLastCommitEmail()); attributes.putIfAbsent("COMMIT_TIME", gitInfo.getLastCommitDateTime()); attributes.putIfAbsent("COMMIT_TIMESTAMP", String.valueOf(gitInfo.getLastCommitTime())); attributes.putIfAbsent("build-user", gitInfo.getBuildUser()); attributes.putIfAbsent("build-email", gitInfo.getBuildEmail()); } } private String determineSpringBootVersion() { String version = getClass().getPackage().getImplementationVersion(); return (version != null) ? version : "unknown"; } public CopyAction createCopyAction(Jar jar) throws IOException { return createCopyAction(jar, null); } public CopyAction createCopyAction(Jar jar, String layerToolsLocation) throws IOException { File bizOutput = getTargetFile(jar, this.arkExtension.getBizClassifier().get()); File arkOutput = getTargetFile(jar, this.arkExtension.getArkClassifier().get()); Manifest manifest = jar.getManifest(); boolean preserveFileTimestamps = jar.isPreserveFileTimestamps(); Integer dirMode = getDirMode(jar); Integer fileMode = getFileMode(jar); boolean includeDefaultLoader = isUsingDefaultLoader(jar); Spec requiresUnpack = this.requiresUnpack.getAsSpec(); Spec exclusions = this.exclusions.getAsExcludeSpec(); Spec librarySpec = this.librarySpec; Function compressionResolver = this.compressionResolver; String encoding = jar.getMetadataCharset(); CopyAction action = new ArkBizCopyAction(bizOutput,arkOutput, manifest, preserveFileTimestamps, dirMode, fileMode, includeDefaultLoader, requiresUnpack, exclusions, librarySpec, compressionResolver, encoding, this.arkManifest, pluginFiles, bizFiles, conFile); return jar.isReproducibleFileOrder() ? new ReproducibleOrderingCopyAction(action) : action; } private File getTargetFile(Jar jar, String classifier) { File outputDir = this.arkExtension.getOutputDirectory() .getOrElse(jar.getDestinationDirectory().get()) .getAsFile(); if (!outputDir.exists()) { boolean created = outputDir.mkdirs(); if (!created) { throw new GradleException("Failed to create output directory: " + outputDir.getAbsolutePath()); } System.out.println("Created output directory: " + outputDir.getAbsolutePath()); } File targetFile = new File(outputDir, getArkBizName(jar, classifier)); System.out.println("Target file will be created at: " + targetFile.getAbsolutePath()); return targetFile; } private String getArkBizName(Jar jar, String classifier){ String name = ""; name += maybe(name, jar.getArchiveBaseName().getOrNull()); name += maybe(name, jar.getArchiveAppendix().getOrNull()); name += maybe(name, jar.getArchiveVersion().getOrNull()); name += maybe(name, jar.getArchiveClassifier().getOrNull()); name += maybe(name, classifier); String extension = jar.getArchiveExtension().getOrNull(); name += (isTrue(extension) ? "." + extension : ""); return name; } private Boolean isTrue(Object object){ if (object instanceof String) { return !((String) object).isEmpty(); } return false; } private String maybe(String prefix, String value) { if (isTrue(value)) { return isTrue(prefix) ? "-".concat(value) : value; } else { return ""; } } private Integer getDirMode(CopySpec copySpec) { return getMode(copySpec, "getDirPermissions", copySpec::getDirMode); } private Integer getFileMode(CopySpec copySpec) { return getMode(copySpec, "getFilePermissions", copySpec::getFileMode); } @SuppressWarnings("unchecked") private Integer getMode(CopySpec copySpec, String methodName, Supplier fallback) { if (GradleVersion.current().compareTo(GradleVersion.version("8.3")) >= 0) { try { Object filePermissions = ((Property) copySpec.getClass().getMethod(methodName).invoke(copySpec)) .getOrNull(); return (filePermissions != null) ? (int) filePermissions.getClass().getMethod("toUnixNumeric").invoke(filePermissions) : null; } catch (Exception ex) { throw new GradleException("Failed to get permissions", ex); } } return fallback.get(); } private boolean isUsingDefaultLoader(Jar jar) { return DEFAULT_LAUNCHER_CLASSES.contains(jar.getManifest().getAttributes().get("Main-Class")); } void requiresUnpack(String... patterns) { this.requiresUnpack.include(patterns); } void requiresUnpack(Spec spec) { this.requiresUnpack.include(spec); } void excludeNonZipLibraryFiles(FileCopyDetails details) { if (this.librarySpec.isSatisfiedBy(details)) { excludeNonZipFiles(details); } } public void excludeNonZipFiles(FileCopyDetails details) { if (!isZip(details.getFile()) || isSofaArk(details.getFile())) { details.exclude(); } } private boolean isZip(File file) { try { try (FileInputStream fileInputStream = new FileInputStream(file)) { return isZip(fileInputStream); } } catch (IOException ex) { return false; } } private boolean isZip(InputStream inputStream) throws IOException { for (byte headerByte : ZIP_FILE_HEADER) { if (inputStream.read() != headerByte) { return false; } } return true; } private boolean isSofaArk(File jarFile){ try (JarFile jar = new JarFile(jarFile)) { for (JarEntry entry : Collections.list(jar.entries())) { if (entry.getName().contains(BIZ_MARKER)) { bizFiles.add(jarFile); return true; } else if (entry.getName().contains(PLUGIN_MARKER)) { pluginFiles.add(jarFile); return true; } else if (entry.getName().contains(CONTAINER_MARK)){ conFile.add(jarFile); return true; } } } catch (IOException e) { } return false; } public void moveModuleInfoToRoot(CopySpec spec) { spec.filesMatching("module-info.class", this::moveToRoot); } public void moveToRoot(FileCopyDetails details) { details.setRelativePath(details.getRelativeSourcePath()); } /** * {@link CopyAction} variant that sorts entries to ensure reproducible ordering. */ private static final class ReproducibleOrderingCopyAction implements CopyAction { private final CopyAction delegate; private ReproducibleOrderingCopyAction(CopyAction delegate) { this.delegate = delegate; } @Override public WorkResult execute(CopyActionProcessingStream stream) { return this.delegate.execute((action) -> { Map detailsByPath = new TreeMap<>(); stream.process((details) -> detailsByPath.put(details.getRelativePath(), details)); detailsByPath.values().forEach(action::processFile); }); } } } ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/src/main/java/com/alipay/sofa/ark/plugin/ArkBizCopyAction.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin; import com.alipay.sofa.ark.common.util.FileUtils; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.Function; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarInputStream; import java.util.zip.CRC32; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.apache.commons.compress.archivers.zip.UnixStat; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.gradle.api.GradleException; import org.gradle.api.file.FileCopyDetails; import org.gradle.api.file.FileTreeElement; import org.gradle.api.internal.file.copy.CopyAction; import org.gradle.api.internal.file.copy.CopyActionProcessingStream; import org.gradle.api.java.archives.Manifest; import org.gradle.api.specs.Spec; import org.gradle.api.tasks.WorkResult; import org.gradle.api.tasks.WorkResults; import org.gradle.util.GradleVersion; public class ArkBizCopyAction implements CopyAction { static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = OffsetDateTime.of(1980, 2, 1, 0, 0, 0, 0, ZoneOffset.UTC) .toInstant() .toEpochMilli(); private final File bizOutput; private final Manifest manifest; private final boolean preserveFileTimestamps; private final Integer dirMode; private final Integer fileMode; private final boolean includeDefaultLoader; private final Spec requiresUnpack; private final Spec exclusions; private final Spec librarySpec; private final Function compressionResolver; private final String encoding; private final File arkOutput; private String arkBootFile; private final java.util.jar.Manifest arkManifest; private List pluginFiles; private List bizFiles; private List conFile; ArkBizCopyAction(File bizOutput,File arkOutput, Manifest manifest, boolean preserveFileTimestamps, Integer dirMode, Integer fileMode, boolean includeDefaultLoader, Spec requiresUnpack, Spec exclusions, Spec librarySpec, Function compressionResolver, String encoding, java.util.jar.Manifest arkManifest, List pluginFiles, List bizFiles, List conFile ) throws IOException { this.bizOutput = bizOutput; this.arkOutput = arkOutput; this.manifest = manifest; this.preserveFileTimestamps = preserveFileTimestamps; this.dirMode = dirMode; this.fileMode = fileMode; this.includeDefaultLoader = includeDefaultLoader; this.requiresUnpack = requiresUnpack; this.exclusions = exclusions; this.librarySpec = librarySpec; this.compressionResolver = compressionResolver; this.encoding = encoding; this.arkManifest = arkManifest; this.pluginFiles = pluginFiles; this.bizFiles = bizFiles; this.conFile = conFile; } @Override public WorkResult execute(CopyActionProcessingStream copyActions) { try { writeBizArchive(copyActions); writeArkArchive(); return WorkResults.didWork(true); } catch (IOException ex) { throw new GradleException("Failed to create " + this.bizOutput, ex); } } private void writeBizArchive(CopyActionProcessingStream copyActions) throws IOException { OutputStream output = new FileOutputStream(this.bizOutput); try { writeArchive(copyActions, output); } finally { closeQuietly(output); } } private void writeArkArchive() throws IOException { OutputStream output = new FileOutputStream(this.arkOutput); try { writeArkArchive(output); } finally { closeQuietly(output); } } private void writeArkArchive(OutputStream output) throws IOException { ZipArchiveOutputStream zipOutput = new ZipArchiveOutputStream(output); try { setEncodingIfNecessary(zipOutput); Processor1 processor = new Processor1(zipOutput); processor.process(); } finally { closeQuietly(zipOutput); } } private void writeArchive(CopyActionProcessingStream copyActions, OutputStream output) throws IOException { ZipArchiveOutputStream zipOutput = new ZipArchiveOutputStream(output); try { setEncodingIfNecessary(zipOutput); Processor processor = new Processor(zipOutput); copyActions.process(processor::process); processor.finish(); } finally { closeQuietly(zipOutput); } } private void closeQuietly(OutputStream outputStream) { try { outputStream.close(); } catch (IOException ex) { } } private void setEncodingIfNecessary(ZipArchiveOutputStream zipOutputStream) { if (this.encoding != null) { zipOutputStream.setEncoding(this.encoding); } } private class Processor1{ private final ZipArchiveOutputStream out; private LoaderZipEntries.WrittenEntries writtenLoaderEntries; private final Set writtenDirectories = new LinkedHashSet<>(); private final Set writtenLibraries = new LinkedHashSet<>(); private String arkFile; Processor1(ZipArchiveOutputStream out) throws IOException { this.out = out; this.arkFile = conFile.get(0).getAbsolutePath(); } void process() throws IOException { writePluginJar(); writeBootstrapEntry(); writeArkManifest(); writeContainer(); writeBizJar(); } void writePluginJar() throws IOException { writeFiles(pluginFiles, "SOFA-ARK/plugin/"); } void writeArkManifest() throws IOException { ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry("META-INF/MANIFEST.MF"); this.out.putArchiveEntry(zipArchiveEntry); ArkBizCopyAction.this.arkManifest.write(this.out); this.out.closeArchiveEntry(); } private void writeBootstrapEntry() throws IOException { try (JarFile jarFileSource = new JarFile(this.arkFile)){ Enumeration entries = jarFileSource.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry.getName().contains("sofa-ark-archive") || entry.getName().contains("sofa-ark-spi") || entry.getName().contains("sofa-ark-common")) { JarInputStream inputStream = new JarInputStream(new BufferedInputStream( jarFileSource.getInputStream(entry))); writeLoaderClasses(inputStream, jarFileSource); } } } catch (NullPointerException exception){ throw new RuntimeException("No sofa-ark-all file find, please configure it"); } } void writeContainer() throws IOException { File file = new File(arkFile); try( FileInputStream fileInputStream = new FileInputStream(file)) { ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry("SOFA-ARK/container/"+ file.getName()); writeEntry(fileInputStream, zipArchiveEntry); } } void writeBizJar() throws IOException { bizFiles.add(bizOutput); writeFiles(bizFiles, "SOFA-ARK/biz/"); } void writeFiles(List files, String path){ for(File file : files){ try( FileInputStream fileInputStream = new FileInputStream(file)) { ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(path + file.getName()); writeEntry(fileInputStream, zipArchiveEntry); } catch (IOException e) { throw new RuntimeException(e); } } } private void writeEntry(FileInputStream fileInputStream, ZipArchiveEntry zipArchiveEntry) throws IOException { this.out.putArchiveEntry(zipArchiveEntry); byte[] buffer = new byte[1024]; int len; while ((len = fileInputStream.read(buffer)) > 0) { this.out.write(buffer, 0, len); } this.out.closeArchiveEntry(); } private void writeDirectory(ZipArchiveEntry entry, ZipArchiveOutputStream out) throws IOException { out.putArchiveEntry(entry); out.closeArchiveEntry(); } private void writeClass(ZipArchiveEntry entry, ZipInputStream in, ZipArchiveOutputStream out) throws IOException { out.putArchiveEntry(entry); StringUtils.copyTo(in, out); out.closeArchiveEntry(); } private int getDirMode() { return (ArkBizCopyAction.this.dirMode != null) ? ArkBizCopyAction.this.dirMode : UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM; } private void writeLoaderClasses(JarInputStream jarInputStream, JarFile jarFileSource) throws IOException { JarEntry entry; while ((entry = jarInputStream.getNextJarEntry()) != null) { if (entry.getName().endsWith(".class") && (entry.getName().contains("com/alipay/sofa/ark/spi/archive") || entry.getName().contains("com/alipay/sofa/ark/loader") || entry.getName().contains("com/alipay/sofa/ark/bootstrap") || entry.getName().contains("com/alipay/sofa/ark/common/util/StringUtils") || entry.getName().contains("com/alipay/sofa/ark/common/util/AssertUtils") || entry .getName().contains("com/alipay/sofa/ark/spi/constant"))) { ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(entry.getName()); this.out.putArchiveEntry(zipArchiveEntry); byte[] bytes = new byte[1024]; int length; while ((length = jarInputStream.read(bytes)) >= 0) { this.out.write(bytes, 0, length); } this.out.closeArchiveEntry(); } } jarInputStream.close(); } } private class Processor { private final ZipArchiveOutputStream out; private LoaderZipEntries.WrittenEntries writtenLoaderEntries; private final Set writtenDirectories = new LinkedHashSet<>(); private final Set writtenLibraries = new LinkedHashSet<>(); Processor(ZipArchiveOutputStream out) throws IOException { this.out = out; } void process(FileCopyDetails details) { if (skipProcessing(details)) { return; } try { if (details.isDirectory()) { processDirectory(details); } else { processFile(details); } } catch (IOException ex) { throw new GradleException("Failed to add " + details + " to " + ArkBizCopyAction.this.bizOutput, ex); } } private boolean skipProcessing(FileCopyDetails details) { return ArkBizCopyAction.this.exclusions.isSatisfiedBy(details) || (this.writtenLoaderEntries != null && this.writtenLoaderEntries.isWrittenDirectory(details)); } private void processDirectory(FileCopyDetails details) throws IOException { String name = details.getRelativePath().getPathString(); ZipArchiveEntry entry = new ZipArchiveEntry(name + '/'); prepareEntry(entry, name, getTime(details), getFileMode(details)); this.out.putArchiveEntry(entry); this.out.closeArchiveEntry(); this.writtenDirectories.add(name); } private void processFile(FileCopyDetails details) throws IOException { String name = details.getRelativePath().getPathString(); ZipArchiveEntry entry = new ZipArchiveEntry(name); prepareEntry(entry, name, getTime(details), getFileMode(details)); ZipCompression compression = ArkBizCopyAction.this.compressionResolver.apply(details); if (compression == ZipCompression.STORED) { prepareStoredEntry(details, entry); } this.out.putArchiveEntry(entry); details.copyTo(this.out); this.out.closeArchiveEntry(); if (ArkBizCopyAction.this.librarySpec.isSatisfiedBy(details)) { this.writtenLibraries.add(name); } } private String getParentDirectory(String name) { int lastSlash = name.lastIndexOf('/'); if (lastSlash == -1) { return null; } return name.substring(0, lastSlash); } private void prepareEntry(ZipArchiveEntry entry, String name, Long time, int mode) throws IOException { writeParentDirectoriesIfNecessary(name, time); entry.setUnixMode(mode); if (time != null) { entry.setTime(DefaultTimeZoneOffset.INSTANCE.removeFrom(time)); } } private void writeParentDirectoriesIfNecessary(String name, Long time) throws IOException { String parentDirectory = getParentDirectory(name); if (parentDirectory != null && this.writtenDirectories.add(parentDirectory)) { ZipArchiveEntry entry = new ZipArchiveEntry(parentDirectory + '/'); prepareEntry(entry, parentDirectory, time, getDirMode()); this.out.putArchiveEntry(entry); this.out.closeArchiveEntry(); } } void finish() throws IOException { writeArkBizMark(); } private void writeArkBizMark() throws IOException { String info = "a mark file included in sofa-ark module."; String name = "com/alipay/sofa/ark/biz/mark"; ZipArchiveEntry entry = new ZipArchiveEntry(name); prepareEntry(entry, name, getTime(), getFileMode()); this.out.putArchiveEntry(entry); byte[] data = info.getBytes(StandardCharsets.UTF_8); this.out.write(data, 0, data.length); this.out.closeArchiveEntry(); } private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException { prepareStoredEntry(details.open(), archiveEntry); if (ArkBizCopyAction.this.requiresUnpack.isSatisfiedBy(details)) { archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile())); } } private void prepareStoredEntry(InputStream input, ZipArchiveEntry archiveEntry) throws IOException { new CrcAndSize(input).setUpStoredEntry(archiveEntry); } private Long getTime() { return getTime(null); } private Long getTime(FileCopyDetails details) { if (!ArkBizCopyAction.this.preserveFileTimestamps) { return CONSTANT_TIME_FOR_ZIP_ENTRIES; } if (details != null) { return details.getLastModified(); } return null; } private int getDirMode() { return (ArkBizCopyAction.this.dirMode != null) ? ArkBizCopyAction.this.dirMode : UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM; } private int getFileMode() { return (ArkBizCopyAction.this.fileMode != null) ? ArkBizCopyAction.this.fileMode : UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM; } private int getFileMode(FileCopyDetails details) { return (ArkBizCopyAction.this.fileMode != null) ? ArkBizCopyAction.this.fileMode : UnixStat.FILE_FLAG | getPermissions(details); } private int getPermissions(FileCopyDetails details) { if (GradleVersion.current().compareTo(GradleVersion.version("8.3")) >= 0) { try { Object permissions = details.getClass().getMethod("getPermissions").invoke(details); return (int) permissions.getClass().getMethod("toUnixNumeric").invoke(permissions); } catch (Exception ex) { throw new GradleException("Failed to get permissions", ex); } } return details.getMode(); } } /** * Data holder for CRC and Size. */ private static class CrcAndSize { private static final int BUFFER_SIZE = 32 * 1024; private final CRC32 crc = new CRC32(); private long size; CrcAndSize(InputStream inputStream) throws IOException { try { load(inputStream); } finally { inputStream.close(); } } private void load(InputStream inputStream) throws IOException { byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { this.crc.update(buffer, 0, bytesRead); this.size += bytesRead; } } void setUpStoredEntry(ZipArchiveEntry entry) { entry.setSize(this.size); entry.setCompressedSize(this.size); entry.setCrc(this.crc.getValue()); entry.setMethod(ZipEntry.STORED); } } } ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/src/main/java/com/alipay/sofa/ark/plugin/ArkJar.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.concurrent.Callable; import java.util.function.Function; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.file.CopySpec; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileCopyDetails; import org.gradle.api.file.FileTreeElement; import org.gradle.api.internal.file.copy.CopyAction; import org.gradle.api.provider.Property; import org.gradle.api.specs.Spec; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.bundling.Jar; public class ArkJar extends Jar implements BootArchive { private static final String LAUNCHER = "com.alipay.sofa.ark.bootstrap.ArkLauncher"; private static final String CLASSES_DIRECTORY = "classes/"; private static final String LIB_DIRECTORY = "lib/"; private static final String ARK_BIZ_MARK = "com/alipay/sofa/ark/biz/mark"; private static final String CLASSPATH_INDEX = "classpath.idx"; private final ArkArchiveSupport support; private final CopySpec bootInfSpec; private final Property mainClass; private FileCollection classpath; private File gitInfo; private SofaArkGradlePluginExtension arkExtension; /** * Creates a new {@code BootJar} task. */ public ArkJar() { Project project = getProject(); this.gitInfo = getGitDirectory(project); this.arkExtension = project.getExtensions().findByType(SofaArkGradlePluginExtension.class); this.support = new ArkArchiveSupport(LAUNCHER, new LibrarySpec(), new ZipCompressionResolver(), gitInfo, arkExtension); this.bootInfSpec = project.copySpec().into(""); this.mainClass = project.getObjects().property(String.class); configureBootInfSpec(this.bootInfSpec); getMainSpec().with(this.bootInfSpec); } private File getGitDirectory(Project project) { File projectDir = project.getRootDir(); File gitFolder = new File(projectDir, ".git"); if (gitFolder.exists() && gitFolder.isDirectory()) { return gitFolder; } return new File(" "); } private void configureBootInfSpec(CopySpec bootInfSpec) { bootInfSpec.into("", fromCallTo(this::classpathDirectories)); bootInfSpec.into("lib", fromCallTo(this::classpathFiles)).eachFile(this.support::excludeNonZipFiles); this.support.moveModuleInfoToRoot(bootInfSpec); moveMetaInfToRoot(bootInfSpec); } private Iterable classpathDirectories() { return classpathEntries(File::isDirectory); } private Iterable classpathFiles() { return classpathEntries(File::isFile); } private Iterable classpathEntries(Spec filter) { return (this.classpath != null) ? this.classpath.filter(filter) : Collections.emptyList(); } private void moveMetaInfToRoot(CopySpec spec) { spec.eachFile((file) -> { String path = file.getRelativeSourcePath().getPathString(); if (path.startsWith("META-INF/") && !path.equals("META-INF/aop.xml") && !path.endsWith(".kotlin_module") && !path.startsWith("META-INF/services/")) { this.support.moveToRoot(file); } }); } @Override public void copy() { this.support.configureBizManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY, CLASSPATH_INDEX); super.copy(); } @Override protected CopyAction createCopyAction() { try { return this.support.createCopyAction(this); } catch (IOException e) { throw new RuntimeException(e); } } @Override public Property getMainClass() { return this.mainClass; } @Override public void requiresUnpack(String... patterns) { this.support.requiresUnpack(patterns); } @Override public void requiresUnpack(Spec spec) { this.support.requiresUnpack(spec); } @Override public FileCollection getClasspath() { return this.classpath; } @Override public void classpath(Object... classpath) { FileCollection existingClasspath = this.classpath; this.classpath = getProject().files((existingClasspath != null) ? existingClasspath : Collections.emptyList(), classpath); } @Override public void setClasspath(Object classpath) { this.classpath = getProject().files(classpath); } @Override public void setClasspath(FileCollection classpath) { this.classpath = getProject().files(classpath); } /** * Returns a {@code CopySpec} that can be used to add content to the {@code BOOT-INF} * directory of the jar. * @return a {@code CopySpec} for {@code BOOT-INF} * @since 2.0.3 */ @Internal public CopySpec getBootInf() { CopySpec child = getProject().copySpec(); this.bootInfSpec.with(child); return child; } /** * Return the {@link ZipCompression} that should be used when adding the file * represented by the given {@code details} to the jar. By default, any * {@link #isLibrary(FileCopyDetails) library} is {@link ZipCompression#STORED stored} * and all other files are {@link ZipCompression#DEFLATED deflated}. * @param details the file copy details * @return the compression to use */ protected ZipCompression resolveZipCompression(FileCopyDetails details) { return isLibrary(details) ? ZipCompression.STORED : ZipCompression.DEFLATED; } /** * Return if the {@link FileCopyDetails} are for a library. By default any file in * {@code BOOT-INF/lib} is considered to be a library. * @param details the file copy details * @return {@code true} if the details are for a library * @since 2.3.0 */ protected boolean isLibrary(FileCopyDetails details) { String path = details.getRelativePath().getPathString(); return path.startsWith(LIB_DIRECTORY); } /** * Syntactic sugar that makes {@link CopySpec#into} calls a little easier to read. * @param the result type * @param callable the callable * @return an action to add the callable to the spec */ private static Action fromCallTo(Callable callable) { return (spec) -> spec.from(callTo(callable)); } /** * Syntactic sugar that makes {@link CopySpec#from} calls a little easier to read. * @param the result type * @param callable the callable * @return the callable */ private static Callable callTo(Callable callable) { return callable; } private final class LibrarySpec implements Spec { @Override public boolean isSatisfiedBy(FileCopyDetails details) { return isLibrary(details); } } private final class ZipCompressionResolver implements Function { @Override public ZipCompression apply(FileCopyDetails details) { return resolveZipCompression(details); } } } ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/src/main/java/com/alipay/sofa/ark/plugin/ArkPluginAction.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin; import java.io.File; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.attributes.Bundling; import org.gradle.api.attributes.LibraryElements; import org.gradle.api.attributes.Usage; import org.gradle.api.file.FileCollection; import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.BasePlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.util.GradleVersion; public class ArkPluginAction implements Action { private static final String PARAMETERS_COMPILER_ARG = "-parameters"; @Override public void execute(Project project) { classifyJarTask(project); configureDevelopmentOnlyConfiguration(project); project.afterEvaluate(this::configureArkAllArtifact); project.afterEvaluate(this::configureBootJarTask); configureBuildTask(project); project.afterEvaluate(this::configureUtf8Encoding); configureParametersCompilerArg(project); configureAdditionalMetadataLocations(project); } private void classifyJarTask(Project project) { project.getTasks() .named(JavaPlugin.JAR_TASK_NAME, Jar.class) .configure((task) -> task.getArchiveClassifier().convention("plain")); } private void configureArkAllArtifact(Project project) { SofaArkGradlePluginExtension arkConfig = project.getExtensions().getByType(SofaArkGradlePluginExtension.class); Configuration runtimeClasspath = project.getConfigurations().getByName("runtimeClasspath"); Configuration sofaArkConfig = project.getConfigurations().maybeCreate("sofaArkConfig"); // TODO: configure as the version of sofa-ark Dependency arkDependency = project.getDependencies().create(SofaArkGradlePlugin.ARK_BOOTSTRAP+"2.2.14"); ((ModuleDependency) arkDependency).setTransitive(false); sofaArkConfig.getDependencies().add(arkDependency); runtimeClasspath.extendsFrom(sofaArkConfig); } private void configureBuildTask(Project project) { project.getTasks() .named(BasePlugin.ASSEMBLE_TASK_NAME) .configure((task) -> task.dependsOn(project.getTasks().getByName("arkJar"))); } private void configureBootJarTask(Project project) { SofaArkGradlePluginExtension arkExtension = project.getExtensions().findByType(SofaArkGradlePluginExtension.class); Configuration configuration = project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); applyExclusions(configuration, arkExtension); SourceSet mainSourceSet = sourceSets(project).getByName(SourceSet.MAIN_SOURCE_SET_NAME); Configuration developmentOnly = project.getConfigurations() .getByName(SofaArkGradlePlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); Configuration productionRuntimeClasspath = project.getConfigurations() .getByName(SofaArkGradlePlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME); Callable classpath = () -> mainSourceSet.getRuntimeClasspath() .minus((developmentOnly.minus(productionRuntimeClasspath))) .filter(new JarTypeFileSpec()); TaskProvider resolveMainClassName = ResolveMainClassName .registerForTask(SofaArkGradlePlugin.ARK_BIZ_TASK_NAME, project, classpath); project.getTasks().register(SofaArkGradlePlugin.ARK_BIZ_TASK_NAME, ArkJar.class, (arkJar) -> { arkJar.setDescription( "Assembles an executable jar archive containing the main classes and their dependencies."); arkJar.setGroup(BasePlugin.BUILD_GROUP); arkJar.classpath(classpath); Provider manifestStartClass = project .provider(() -> (String) arkJar.getManifest().getAttributes().get("Start-Class")); arkJar.getMainClass() .convention(resolveMainClassName.flatMap((resolver) -> manifestStartClass.isPresent() ? manifestStartClass : resolveMainClassName.get().readMainClassName())); }); } private void applyExclusions(Configuration configuration, SofaArkGradlePluginExtension arkConfig) { for (String exclude : arkConfig.getExcludes().get()) { String[] parts = exclude.split(":"); // TODO: compatible with group:module:version if (parts.length == 2) { Map excludeMap = new HashMap<>(); excludeMap.put("group", parts[0]); excludeMap.put("module", parts[1]); configuration.exclude(excludeMap); } } arkConfig.getExcludeGroupIds().get().stream() .map(groupId -> Collections.singletonMap("group", groupId)) .forEach(configuration::exclude); arkConfig.getExcludeArtifactIds().get().stream() .map(artifactId -> Collections.singletonMap("module", artifactId)) .forEach(configuration::exclude); } private SourceSetContainer sourceSets(Project project) { if (GradleVersion.current().compareTo(GradleVersion.version("7.1")) < 0) { return project.getConvention().getPlugin(org.gradle.api.plugins.JavaPluginConvention.class).getSourceSets(); } return project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets(); } private void configureUtf8Encoding(Project evaluatedProject) { evaluatedProject.getTasks().withType(JavaCompile.class).configureEach(this::configureUtf8Encoding); } private void configureUtf8Encoding(JavaCompile compile) { if (compile.getOptions().getEncoding() == null) { compile.getOptions().setEncoding("UTF-8"); } } private void configureParametersCompilerArg(Project project) { project.getTasks().withType(JavaCompile.class).configureEach((compile) -> { List compilerArgs = compile.getOptions().getCompilerArgs(); if (!compilerArgs.contains(PARAMETERS_COMPILER_ARG)) { compilerArgs.add(PARAMETERS_COMPILER_ARG); } }); } private void configureAdditionalMetadataLocations(Project project) { project.afterEvaluate((evaluated) -> evaluated.getTasks() .withType(JavaCompile.class) .configureEach(this::configureAdditionalMetadataLocations)); } private void configureAdditionalMetadataLocations(JavaCompile compile) { sourceSets(compile.getProject()).stream() .filter((candidate) -> candidate.getCompileJavaTaskName().equals(compile.getName())) .map((match) -> match.getResources().getSrcDirs()) .findFirst() .ifPresent((locations) -> compile.doFirst(new AdditionalMetadataLocationsConfigurer(locations))); } private void configureDevelopmentOnlyConfiguration(Project project) { Configuration developmentOnly = project.getConfigurations() .create(SofaArkGradlePlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); developmentOnly .setDescription("Configuration for development-only dependencies such as Spring Boot's DevTools."); Configuration runtimeClasspath = project.getConfigurations() .getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); Configuration productionRuntimeClasspath = project.getConfigurations() .create(SofaArkGradlePlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME); AttributeContainer attributes = productionRuntimeClasspath.getAttributes(); ObjectFactory objectFactory = project.getObjects(); attributes.attribute(Usage.USAGE_ATTRIBUTE, objectFactory.named(Usage.class, Usage.JAVA_RUNTIME)); attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, objectFactory.named(Bundling.class, Bundling.EXTERNAL)); attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objectFactory.named(LibraryElements.class, LibraryElements.JAR)); productionRuntimeClasspath.setVisible(false); productionRuntimeClasspath.setExtendsFrom(runtimeClasspath.getExtendsFrom()); productionRuntimeClasspath.setCanBeResolved(runtimeClasspath.isCanBeResolved()); productionRuntimeClasspath.setCanBeConsumed(runtimeClasspath.isCanBeConsumed()); runtimeClasspath.extendsFrom(developmentOnly); } private static final class AdditionalMetadataLocationsConfigurer implements Action { private final Set locations; private AdditionalMetadataLocationsConfigurer(Set locations) { this.locations = locations; } @Override public void execute(Task task) { if (!(task instanceof JavaCompile)) { return; } JavaCompile compile = (JavaCompile) task; if (hasConfigurationProcessorOnClasspath(compile)) { configureAdditionalMetadataLocations(compile); } } private boolean hasConfigurationProcessorOnClasspath(JavaCompile compile) { Set files = (compile.getOptions().getAnnotationProcessorPath() != null) ? compile.getOptions().getAnnotationProcessorPath().getFiles() : compile.getClasspath().getFiles(); return files.stream() .map(File::getName) .anyMatch((name) -> name.startsWith("spring-boot-configuration-processor")); } private void configureAdditionalMetadataLocations(JavaCompile compile) { compile.getOptions() .getCompilerArgs() .add("-Aorg.springframework.boot.configurationprocessor.additionalMetadataLocations=" + StringUtils.collectionToCommaDelimitedString(this.locations)); } } } ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/src/main/java/com/alipay/sofa/ark/plugin/BootArchive.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileTreeElement; import org.gradle.api.provider.Property; import org.gradle.api.specs.Spec; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; public interface BootArchive extends Task { /** * Returns the fully-qualified name of the application's main class. * @return the fully-qualified name of the application's main class * @since 2.4.0 */ @Input Property getMainClass(); /** * Adds Ant-style patterns that identify files that must be unpacked from the archive * when it is launched. * @param patterns the patterns */ void requiresUnpack(String... patterns); /** * Adds a spec that identifies files that must be unpacked from the archive when it is * launched. * @param spec the spec */ void requiresUnpack(Spec spec); /** * Returns the classpath that will be included in the archive. * @return the classpath */ @Optional @Classpath FileCollection getClasspath(); /** * Adds files to the classpath to include in the archive. The given {@code classpath} * is evaluated as per {@link Project#files(Object...)}. * @param classpath the additions to the classpath */ void classpath(Object... classpath); /** * Sets the classpath to include in the archive. The given {@code classpath} is * evaluated as per {@link Project#files(Object...)}. * @param classpath the classpath * @since 2.0.7 */ void setClasspath(Object classpath); /** * Sets the classpath to include in the archive. * @param classpath the classpath * @since 2.0.7 */ void setClasspath(FileCollection classpath); } ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/src/main/java/com/alipay/sofa/ark/plugin/DefaultTimeZoneOffset.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin; import java.nio.file.attribute.FileTime; import java.util.TimeZone; class DefaultTimeZoneOffset { static final DefaultTimeZoneOffset INSTANCE = new DefaultTimeZoneOffset(TimeZone.getDefault()); private final TimeZone defaultTimeZone; DefaultTimeZoneOffset(TimeZone defaultTimeZone) { this.defaultTimeZone = defaultTimeZone; } /** * Remove the default offset from the given time. * @param time the time to remove the default offset from * @return the time with the default offset removed */ FileTime removeFrom(FileTime time) { return FileTime.fromMillis(removeFrom(time.toMillis())); } /** * Remove the default offset from the given time. * @param time the time to remove the default offset from * @return the time with the default offset removed */ long removeFrom(long time) { return time - this.defaultTimeZone.getOffset(time); } } ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/src/main/java/com/alipay/sofa/ark/plugin/JarTypeFileSpec.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin; import java.io.File; import java.util.Collections; import java.util.Set; import java.util.jar.JarFile; import org.gradle.api.specs.Spec; class JarTypeFileSpec implements Spec { private static final Set EXCLUDED_JAR_TYPES = Collections.singleton("dependencies-starter"); @Override public boolean isSatisfiedBy(File file) { try (JarFile jar = new JarFile(file)) { String jarType = jar.getManifest().getMainAttributes().getValue("Spring-Boot-Jar-Type"); if (jarType != null && EXCLUDED_JAR_TYPES.contains(jarType)) { return false; } } catch (Exception ex) { // Continue } return true; } } ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/src/main/java/com/alipay/sofa/ark/plugin/LoaderZipEntries.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.LinkedHashSet; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.gradle.api.file.FileTreeElement; /** * Internal utility used to copy entries from the {@code spring-boot-loader.jar}. * * @author Andy Wilkinson * @author Phillip Webb * @author Scott Frederick */ class LoaderZipEntries { private final Long entryTime; private final int dirMode; private final int fileMode; LoaderZipEntries(Long entryTime, int dirMode, int fileMode) { this.entryTime = entryTime; this.dirMode = dirMode; this.fileMode = fileMode; } WrittenEntries writeTo(ZipArchiveOutputStream out) throws IOException { WrittenEntries written = new WrittenEntries(); try (ZipInputStream loaderJar = new ZipInputStream( getClass().getResourceAsStream("/META-INF/loader/spring-boot-loader.jar"))) { ZipEntry entry = loaderJar.getNextEntry(); while (entry != null) { if (entry.isDirectory() && !entry.getName().equals("META-INF/")) { writeDirectory(new ZipArchiveEntry(entry), out); written.addDirectory(entry); } else if (entry.getName().endsWith(".class")) { writeClass(new ZipArchiveEntry(entry), loaderJar, out); written.addFile(entry); } entry = loaderJar.getNextEntry(); } } return written; } private void writeDirectory(ZipArchiveEntry entry, ZipArchiveOutputStream out) throws IOException { prepareEntry(entry, this.dirMode); out.putArchiveEntry(entry); out.closeArchiveEntry(); } private void writeClass(ZipArchiveEntry entry, ZipInputStream in, ZipArchiveOutputStream out) throws IOException { prepareEntry(entry, this.fileMode); out.putArchiveEntry(entry); copy(in, out); out.closeArchiveEntry(); } private void prepareEntry(ZipArchiveEntry entry, int unixMode) { if (this.entryTime != null) { entry.setTime(DefaultTimeZoneOffset.INSTANCE.removeFrom(this.entryTime)); } entry.setUnixMode(unixMode); } private void copy(InputStream in, OutputStream out) throws IOException { StringUtils.copyTo(in, out); } /** * Tracks entries that have been written. */ static class WrittenEntries { private final Set directories = new LinkedHashSet<>(); private final Set files = new LinkedHashSet<>(); private void addDirectory(ZipEntry entry) { this.directories.add(entry.getName()); } private void addFile(ZipEntry entry) { this.files.add(entry.getName()); } boolean isWrittenDirectory(FileTreeElement element) { String path = element.getRelativePath().getPathString(); if (element.isDirectory() && !path.endsWith(("/"))) { path += "/"; } return this.directories.contains(path); } Set getFiles() { return this.files; } } } ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/src/main/java/com/alipay/sofa/ark/plugin/MainClassFinder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin; import java.io.BufferedInputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Deque; import java.util.Enumeration; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; /** * Finds any class with a {@code public static main} method by performing a breadth first * search. * * @author Phillip Webb * @author Andy Wilkinson * @since 1.0.0 */ public abstract class MainClassFinder { private static final String DOT_CLASS = ".class"; private static final Type STRING_ARRAY_TYPE = Type.getType(String[].class); private static final Type MAIN_METHOD_TYPE = Type.getMethodType(Type.VOID_TYPE, STRING_ARRAY_TYPE); private static final String MAIN_METHOD_NAME = "main"; private static final FileFilter CLASS_FILE_FILTER = MainClassFinder::isClassFile; private static final FileFilter PACKAGE_DIRECTORY_FILTER = MainClassFinder::isPackageDirectory; private static boolean isClassFile(File file) { return file.isFile() && file.getName().endsWith(DOT_CLASS); } private static boolean isPackageDirectory(File file) { return file.isDirectory() && !file.getName().startsWith("."); } /** * Find the main class from a given directory. * @param rootDirectory the root directory to search * @return the main class or {@code null} * @throws IOException if the directory cannot be read */ public static String findMainClass(File rootDirectory) throws IOException { return doWithMainClasses(rootDirectory, MainClass::getName); } /** * Find a single main class from the given {@code rootDirectory}. * @param rootDirectory the root directory to search * @return the main class or {@code null} * @throws IOException if the directory cannot be read */ public static String findSingleMainClass(File rootDirectory) throws IOException { return findSingleMainClass(rootDirectory, null); } /** * Find a single main class from the given {@code rootDirectory}. A main class * annotated with an annotation with the given {@code annotationName} will be * preferred over a main class with no such annotation. * @param rootDirectory the root directory to search * @param annotationName the name of the annotation that may be present on the main * class * @return the main class or {@code null} * @throws IOException if the directory cannot be read */ public static String findSingleMainClass(File rootDirectory, String annotationName) throws IOException { SingleMainClassCallback callback = new SingleMainClassCallback(annotationName); MainClassFinder.doWithMainClasses(rootDirectory, callback); return callback.getMainClassName(); } /** * Perform the given callback operation on all main classes from the given root * directory. * @param the result type * @param rootDirectory the root directory * @param callback the callback * @return the first callback result or {@code null} * @throws IOException in case of I/O errors */ static T doWithMainClasses(File rootDirectory, MainClassCallback callback) throws IOException { if (!rootDirectory.exists()) { return null; // nothing to do } if (!rootDirectory.isDirectory()) { throw new IllegalArgumentException("Invalid root directory '" + rootDirectory + "'"); } String prefix = rootDirectory.getAbsolutePath() + "/"; Deque stack = new ArrayDeque<>(); stack.push(rootDirectory); while (!stack.isEmpty()) { File file = stack.pop(); if (file.isFile()) { try (InputStream inputStream = new FileInputStream(file)) { ClassDescriptor classDescriptor = createClassDescriptor(inputStream); if (classDescriptor != null && classDescriptor.isMainMethodFound()) { String className = convertToClassName(file.getAbsolutePath(), prefix); T result = callback.doWith(new MainClass(className, classDescriptor.getAnnotationNames())); if (result != null) { return result; } } } } if (file.isDirectory()) { pushAllSorted(stack, file.listFiles(PACKAGE_DIRECTORY_FILTER)); pushAllSorted(stack, file.listFiles(CLASS_FILE_FILTER)); } } return null; } private static void pushAllSorted(Deque stack, File[] files) { Arrays.sort(files, Comparator.comparing(File::getName)); for (File file : files) { stack.push(file); } } /** * Find the main class in a given jar file. * @param jarFile the jar file to search * @param classesLocation the location within the jar containing classes * @return the main class or {@code null} * @throws IOException if the jar file cannot be read */ public static String findMainClass(JarFile jarFile, String classesLocation) throws IOException { return doWithMainClasses(jarFile, classesLocation, MainClass::getName); } /** * Find a single main class in a given jar file. * @param jarFile the jar file to search * @param classesLocation the location within the jar containing classes * @return the main class or {@code null} * @throws IOException if the jar file cannot be read */ public static String findSingleMainClass(JarFile jarFile, String classesLocation) throws IOException { return findSingleMainClass(jarFile, classesLocation, null); } /** * Find a single main class in a given jar file. A main class annotated with an * annotation with the given {@code annotationName} will be preferred over a main * class with no such annotation. * @param jarFile the jar file to search * @param classesLocation the location within the jar containing classes * @param annotationName the name of the annotation that may be present on the main * class * @return the main class or {@code null} * @throws IOException if the jar file cannot be read */ public static String findSingleMainClass(JarFile jarFile, String classesLocation, String annotationName) throws IOException { SingleMainClassCallback callback = new SingleMainClassCallback(annotationName); MainClassFinder.doWithMainClasses(jarFile, classesLocation, callback); return callback.getMainClassName(); } /** * Perform the given callback operation on all main classes from the given jar. * @param the result type * @param jarFile the jar file to search * @param classesLocation the location within the jar containing classes * @param callback the callback * @return the first callback result or {@code null} * @throws IOException in case of I/O errors */ static T doWithMainClasses(JarFile jarFile, String classesLocation, MainClassCallback callback) throws IOException { List classEntries = getClassEntries(jarFile, classesLocation); classEntries.sort(new ClassEntryComparator()); for (JarEntry entry : classEntries) { try (InputStream inputStream = new BufferedInputStream(jarFile.getInputStream(entry))) { ClassDescriptor classDescriptor = createClassDescriptor(inputStream); if (classDescriptor != null && classDescriptor.isMainMethodFound()) { String className = convertToClassName(entry.getName(), classesLocation); T result = callback.doWith(new MainClass(className, classDescriptor.getAnnotationNames())); if (result != null) { return result; } } } } return null; } private static String convertToClassName(String name, String prefix) { name = name.replace('/', '.'); name = name.replace('\\', '.'); name = name.substring(0, name.length() - DOT_CLASS.length()); if (prefix != null) { name = name.substring(prefix.length()); } return name; } private static List getClassEntries(JarFile source, String classesLocation) { classesLocation = (classesLocation != null) ? classesLocation : ""; Enumeration sourceEntries = source.entries(); List classEntries = new ArrayList<>(); while (sourceEntries.hasMoreElements()) { JarEntry entry = sourceEntries.nextElement(); if (entry.getName().startsWith(classesLocation) && entry.getName().endsWith(DOT_CLASS)) { classEntries.add(entry); } } return classEntries; } private static ClassDescriptor createClassDescriptor(InputStream inputStream) { try { ClassReader classReader = new ClassReader(inputStream); ClassDescriptor classDescriptor = new ClassDescriptor(); classReader.accept(classDescriptor, ClassReader.SKIP_CODE); return classDescriptor; } catch (IOException ex) { return null; } } private static class ClassEntryComparator implements Comparator { @Override public int compare(JarEntry o1, JarEntry o2) { Integer d1 = getDepth(o1); Integer d2 = getDepth(o2); int depthCompare = d1.compareTo(d2); if (depthCompare != 0) { return depthCompare; } return o1.getName().compareTo(o2.getName()); } private int getDepth(JarEntry entry) { return entry.getName().split("/").length; } } private static class ClassDescriptor extends ClassVisitor { private final Set annotationNames = new LinkedHashSet<>(); private boolean mainMethodFound; ClassDescriptor() { super(Opcodes.ASM7); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { this.annotationNames.add(Type.getType(desc).getClassName()); return null; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (isAccess(access, Opcodes.ACC_PUBLIC, Opcodes.ACC_STATIC) && MAIN_METHOD_NAME.equals(name) && MAIN_METHOD_TYPE.getDescriptor().equals(desc)) { this.mainMethodFound = true; } return null; } private boolean isAccess(int access, int... requiredOpsCodes) { for (int requiredOpsCode : requiredOpsCodes) { if ((access & requiredOpsCode) == 0) { return false; } } return true; } boolean isMainMethodFound() { return this.mainMethodFound; } Set getAnnotationNames() { return this.annotationNames; } } /** * Callback for handling {@link MainClass MainClasses}. * * @param the callback's return type */ interface MainClassCallback { /** * Handle the specified main class. * @param mainClass the main class * @return a non-null value if processing should end or {@code null} to continue */ T doWith(MainClass mainClass); } /** * A class with a {@code main} method. */ static final class MainClass { private final String name; private final Set annotationNames; /** * Creates a new {@code MainClass} rather represents the main class with the given * {@code name}. The class is annotated with the annotations with the given * {@code annotationNames}. * @param name the name of the class * @param annotationNames the names of the annotations on the class */ MainClass(String name, Set annotationNames) { this.name = name; this.annotationNames = Collections.unmodifiableSet(new HashSet<>(annotationNames)); } String getName() { return this.name; } Set getAnnotationNames() { return this.annotationNames; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } MainClass other = (MainClass) obj; return this.name.equals(other.name); } @Override public int hashCode() { return this.name.hashCode(); } @Override public String toString() { return this.name; } } /** * Find a single main class, throwing an {@link IllegalStateException} if multiple * candidates exist. */ private static final class SingleMainClassCallback implements MainClassCallback { private final Set mainClasses = new LinkedHashSet<>(); private final String annotationName; private SingleMainClassCallback(String annotationName) { this.annotationName = annotationName; } @Override public Object doWith(MainClass mainClass) { this.mainClasses.add(mainClass); return null; } private String getMainClassName() { Set matchingMainClasses = new LinkedHashSet<>(); if (this.annotationName != null) { for (MainClass mainClass : this.mainClasses) { if (mainClass.getAnnotationNames().contains(this.annotationName)) { matchingMainClasses.add(mainClass); } } } if (matchingMainClasses.isEmpty()) { matchingMainClasses.addAll(this.mainClasses); } if (matchingMainClasses.size() > 1) { throw new IllegalStateException( "Unable to find a single main class from the following candidates " + matchingMainClasses); } return (matchingMainClasses.isEmpty() ? null : matchingMainClasses.iterator().next().getName()); } } } ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/src/main/java/com/alipay/sofa/ark/plugin/Nullable.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.annotation.Nonnull; import javax.annotation.meta.TypeQualifierNickname; import javax.annotation.meta.When; @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Nonnull(when = When.MAYBE) @TypeQualifierNickname public @interface Nullable { } ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/src/main/java/com/alipay/sofa/ark/plugin/ResolveMainClassName.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.Objects; import java.util.concurrent.Callable; import org.gradle.api.DefaultTask; import org.gradle.api.InvalidUserDataException; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.Transformer; import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFile; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.plugins.BasePlugin; import org.gradle.api.plugins.JavaApplication; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskProvider; import org.gradle.work.DisableCachingByDefault; @DisableCachingByDefault(because = "Not worth caching") public class ResolveMainClassName extends DefaultTask { private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication"; private final RegularFileProperty outputFile; private final Property configuredMainClass; private FileCollection classpath; /** * Creates a new instance of the {@code ResolveMainClassName} task. */ public ResolveMainClassName() { this.outputFile = getProject().getObjects().fileProperty(); this.configuredMainClass = getProject().getObjects().property(String.class); } /** * Returns the classpath that the task will examine when resolving the main class * name. * @return the classpath */ @Classpath public FileCollection getClasspath() { return this.classpath; } /** * Sets the classpath that the task will examine when resolving the main class name. * @param classpath the classpath */ public void setClasspath(FileCollection classpath) { setClasspath((Object) classpath); } /** * Sets the classpath that the task will examine when resolving the main class name. * The given {@code classpath} is evaluated as per {@link Project#files(Object...)}. * @param classpath the classpath * @since 2.5.10 */ public void setClasspath(Object classpath) { this.classpath = getProject().files(classpath); } /** * Returns the property for the task's output file that will contain the name of the * main class. * @return the output file */ @OutputFile public RegularFileProperty getOutputFile() { return this.outputFile; } /** * Returns the property for the explicitly configured main class name that should be * used in favor of resolving the main class name from the classpath. * @return the configured main class name property */ @Input @Optional public Property getConfiguredMainClassName() { return this.configuredMainClass; } @TaskAction void resolveAndStoreMainClassName() throws IOException { File outputFile = this.outputFile.getAsFile().get(); outputFile.getParentFile().mkdirs(); String mainClassName = resolveMainClassName(); Files.write(outputFile.toPath(), mainClassName.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } private String resolveMainClassName() { String configuredMainClass = this.configuredMainClass.getOrNull(); if (configuredMainClass != null) { return configuredMainClass; } return getClasspath().filter(File::isDirectory) .getFiles() .stream() .map(this::findMainClass) .filter(Objects::nonNull) .findFirst() .orElse(""); } private String findMainClass(File file) { try { // TODO: compatible with non-spring-boot-project return MainClassFinder.findSingleMainClass(file, SPRING_BOOT_APPLICATION_CLASS_NAME); } catch (IOException ex) { return null; } } Provider readMainClassName() { return this.outputFile.map(new ClassNameReader()); } static TaskProvider registerForTask(String taskName, Project project, Callable classpath) { TaskProvider resolveMainClassNameProvider = project.getTasks() .register(taskName + "MainClassName", ResolveMainClassName.class, (resolveMainClassName) -> { resolveMainClassName .setDescription("Resolves the name of the application's main class for the " + taskName + " task."); resolveMainClassName.setGroup(BasePlugin.BUILD_GROUP); resolveMainClassName.setClasspath(classpath); resolveMainClassName.getConfiguredMainClassName().convention(project.provider(() -> { String javaApplicationMainClass = getJavaApplicationMainClass(project); if (javaApplicationMainClass != null) { return javaApplicationMainClass; } SofaArkGradlePluginExtension springBootExtension = project.getExtensions() .findByType(SofaArkGradlePluginExtension.class); return springBootExtension.getMainClass().getOrNull(); })); resolveMainClassName.getOutputFile() .set(project.getLayout().getBuildDirectory().file(taskName + "MainClassName")); }); return resolveMainClassNameProvider; } private static String getJavaApplicationMainClass(Project project) { JavaApplication javaApplication = project.getExtensions().findByType(JavaApplication.class); if (javaApplication == null) { return null; } return javaApplication.getMainClass().getOrNull(); } private static final class ClassNameReader implements Transformer { @Override public String transform(RegularFile file) { if (file.getAsFile().length() == 0) { throw new InvalidUserDataException( "Main class name has not been configured and it could not be resolved"); } Path output = file.getAsFile().toPath(); try { return new String(Files.readAllBytes(output), StandardCharsets.UTF_8); } catch (IOException ex) { throw new RuntimeException("Failed to read main class name from '" + output + "'"); } } } } ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/src/main/java/com/alipay/sofa/ark/plugin/SofaArkGradlePlugin.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin; import org.gradle.api.GradleException; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.internal.impldep.org.bouncycastle.pqc.crypto.newhope.NHSecretKeyProcessor.PartyUBuilder; import org.gradle.util.GradleVersion; public class SofaArkGradlePlugin implements Plugin { public static final String ARK_VERSION = "2.2.14"; public static final String ARK_BIZ_TASK_NAME = "arkJar"; public static final String DEVELOPMENT_ONLY_CONFIGURATION_NAME = "developmentOnly"; public static final String PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME = "productionRuntimeClasspath"; public static final String ARK_BOOTSTRAP = "com.alipay.sofa:sofa-ark-all:"; @Override public void apply(Project project) { verifyGradleVersion(); createAndSetExtension(project); registerPluginActions(project); } private void verifyGradleVersion() { GradleVersion currentVersion = GradleVersion.current(); if (currentVersion.compareTo(GradleVersion.version("6.8")) < 0) { throw new GradleException("Spring Boot plugin requires Gradle 6.8.+ " + "The current version is " + currentVersion); } } private void createAndSetExtension(Project project) { project.getExtensions().create("arkConfig", SofaArkGradlePluginExtension.class, project); } private void registerPluginActions(Project project) { ArkPluginAction arkAction = new ArkPluginAction(); arkAction.execute(project); } } ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/src/main/java/com/alipay/sofa/ark/plugin/SofaArkGradlePluginExtension.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin; import org.gradle.api.Project; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputDirectory; abstract public class SofaArkGradlePluginExtension { private final String ARK_JAR_PLUGIN_VERSION = "2.2.14"; private final Integer PRIORITY = 100; private final String ARK_CLASSIFIER = "ark-executable"; private final String FINAL_NAME = ""; private final String BIZ_NAME = ""; private final String BIZ_CLASSIFIER = "ark-biz"; private final String WEB_CONTEXT_PATH = "/"; private final Property mainClass; public SofaArkGradlePluginExtension(Project project){ this.mainClass = project.getObjects().property(String.class); getPriority().convention(project.provider(() -> PRIORITY)); getArkClassifier().convention(project.provider(() -> ARK_CLASSIFIER)); getFinalName().convention(project.provider(() -> FINAL_NAME)); getBizName().convention(project.provider(() -> BIZ_NAME)); getBizClassifier().convention(project.provider(() -> BIZ_CLASSIFIER)); getBizVersion().convention(project.provider(() -> project.getVersion().toString())); getWebContextPath().convention(project.provider(()-> WEB_CONTEXT_PATH)); getOutputDirectory().convention(project.getLayout().getBuildDirectory().dir("libs")); } public Property getMainClass() { return this.mainClass; } @OutputDirectory abstract public DirectoryProperty getOutputDirectory(); abstract public Property getFinalName(); abstract public Property getArkClassifier(); abstract public Property getWebContextPath(); abstract public Property getBizName(); abstract public Property getBizClassifier(); abstract public Property getBizVersion(); abstract public Property getPriority(); @Optional abstract public SetProperty getExcludes(); @Optional abstract public SetProperty getExcludeArtifactIds(); @Optional abstract public SetProperty getExcludeGroupIds(); @Optional abstract public SetProperty getDenyImportPackages(); @Optional abstract public SetProperty getDenyImportClasses(); @Optional abstract public SetProperty getDenyImportResources(); @Optional abstract public SetProperty getInjectPluginDependencies(); @Optional abstract public SetProperty getInjectPluginExportPackages(); } ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/src/main/java/com/alipay/sofa/ark/plugin/StringUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collection; import java.util.Iterator; public class StringUtils { static int copyTo(InputStream in, OutputStream out) throws IOException { int byteCount = 0; int bytesRead; for(byte[] buffer = new byte[4096]; (bytesRead = in.read(buffer)) != -1; byteCount += bytesRead) { out.write(buffer, 0, bytesRead); } out.flush(); return byteCount; } public static String collectionToCommaDelimitedString(@Nullable Collection coll) { return collectionToDelimitedString(coll, ","); } public static String collectionToDelimitedString(@Nullable Collection coll, String delim) { return collectionToDelimitedString(coll, delim, "", ""); } public static String collectionToDelimitedString( @Nullable Collection coll, String delim, String prefix, String suffix) { if (CollectionUtils.isEmpty(coll)) { return ""; } int totalLength = coll.size() * (prefix.length() + suffix.length()) + (coll.size() - 1) * delim.length(); for (Object element : coll) { totalLength += String.valueOf(element).length(); } StringBuilder sb = new StringBuilder(totalLength); Iterator it = coll.iterator(); while (it.hasNext()) { sb.append(prefix).append(it.next()).append(suffix); if (it.hasNext()) { sb.append(delim); } } return sb.toString(); } static class CollectionUtils { public static boolean isEmpty(@Nullable Collection collection) { return (collection == null || collection.isEmpty()); } } } ================================================ FILE: sofa-ark-parent/support/ark-gradle-plugin/src/main/java/com/alipay/sofa/ark/plugin/ZipCompression.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin; import java.util.zip.ZipEntry; public enum ZipCompression { /** * The entry should be {@link ZipEntry#STORED} in the archive. */ STORED, /** * The entry should be {@link ZipEntry#DEFLATED} in the archive. */ DEFLATED } ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-maven-plugin` **Packaging**: `maven-plugin` **Goal**: `repackage` This Maven plugin packages applications into Ark executable JARs and Biz modules. ## Purpose - Transform a Spring Boot or plain Java application into an Ark executable JAR - Create Biz module JARs for dynamic deployment - Configure dependency filtering and classloader isolation ## Key Classes ### `RepackageMojo` Main Maven Mojo bound to `package` phase: - Input: Compiled application JAR - Output: `*-ark-executable.jar` (Fat Jar) and/or `*-ark-biz.jar` (Biz module) Key configuration parameters: - `bizName` / `bizVersion` - Module identity - `priority` - Startup priority (default: 100) - `mainClass` - Entry point class - `excludes` / `excludeGroupIds` / `excludeArtifactIds` - Dependencies to exclude - `includes` / `includeGroupIds` / `includeArtifactIds` - Dependencies to include - `denyImportPackages/classes/Resources` - Classloader filtering - `skipArkExecutable` - Skip creating Fat Jar - `keepArkBizJar` - Keep Biz JAR after packaging - `declaredMode` - Enable declared dependency mode - `webContextPath` - Web context path for web apps ### `ModuleSlimExecutor` Handles dependency slimming - removing unnecessary dependencies from the package. ### `ModuleSlimConfig` Configuration for dependency slimming: - `packExcludesConfig` - Exclude rules file - `baseDependencyParentIdentity` - Base dependency filtering ### `ArtifactsLibraries` Handles library resolution and unpacking. ### `model.ArkConfigHolder` Loads Ark configuration from `conf/ark/bootstrap.yml` or `bootstrap.properties`. ## Usage ```xml com.alipay.sofa sofa-ark-maven-plugin repackage my-app 1.0.0 ``` ## Dependencies - `sofa-ark-common` - Utilities - `sofa-ark-tools` - Repackaging logic - Maven Core/Plugin APIs ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/pom.xml ================================================ sofa-ark-support com.alipay.sofa ${sofa.ark.version} 4.0.0 maven-plugin sofa-ark-maven-plugin ${project.groupId}:${project.artifactId} com.alipay.sofa sofa-ark-common com.alipay.sofa sofa-ark-spi com.google.inject guice com.alipay.sofa log-sofa-boot-starter org.slf4j slf4j-log4j12 com.alipay.sofa sofa-ark-tools org.apache.maven maven-archiver org.apache.maven maven-artifact org.apache.maven maven-plugin-api org.apache.maven maven-core org.apache.maven.shared maven-invoker org.apache.maven.plugin-tools maven-plugin-annotations org.apache.maven.plugins maven-dependency-plugin org.apache.maven.shared maven-common-artifact-filters org.sonatype.plexus plexus-build-api org.mockito mockito-inline test junit junit test com.fasterxml.jackson.core jackson-databind org.yaml snakeyaml org.apache.maven.plugins maven-plugin-plugin true mojo-descriptor descriptor ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/main/java/com/alipay/sofa/ark/boot/mojo/ArtifactsLibraries.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import java.io.IOException; import java.util.*; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.Dependency; import org.apache.maven.plugin.logging.Log; import com.alipay.sofa.ark.tools.Libraries; import com.alipay.sofa.ark.tools.Library; import com.alipay.sofa.ark.tools.LibraryCallback; import com.alipay.sofa.ark.tools.LibraryScope; /** * {@link Libraries} backed by Maven {@link Artifact}s. * * @author Phillip Webb * @author Andy Wilkinson * @author Stephane Nicoll */ public class ArtifactsLibraries implements Libraries { private static final Map SCOPES; static { Map libraryScopes = new HashMap<>(); libraryScopes.put(Artifact.SCOPE_COMPILE, LibraryScope.COMPILE); libraryScopes.put(Artifact.SCOPE_RUNTIME, LibraryScope.RUNTIME); libraryScopes.put(Artifact.SCOPE_PROVIDED, LibraryScope.PROVIDED); libraryScopes.put(Artifact.SCOPE_SYSTEM, LibraryScope.COMPILE); SCOPES = Collections.unmodifiableMap(libraryScopes); } private final Set artifacts; private final Collection unpacks; private final Log log; public ArtifactsLibraries(Set artifacts, Collection unpacks, Log log) { this.artifacts = artifacts; this.unpacks = unpacks; this.log = log; } @Override public void doWithLibraries(LibraryCallback callback) throws IOException { Set duplicates = getDuplicates(artifacts); for (Artifact artifact : this.artifacts) { LibraryScope scope = SCOPES.get(artifact.getScope()); if (scope != null && artifact.getFile() != null) { String name = getFileName(artifact); if (duplicates.contains(name)) { this.log.debug(String.format("Duplicate found: %s", name)); name = artifact.getGroupId() + "-" + name; this.log.debug(String.format("Renamed to: %s", name)); } Library library = new Library(name, artifact.getFile(), scope, isUnpackRequired(artifact)); library.setArtifactId(artifact.getArtifactId()); callback.library(library); } } } private Set getDuplicates(Set artifacts) { Set duplicates = new HashSet<>(); Set seen = new HashSet<>(); for (Artifact artifact : artifacts) { String fileName = getFileName(artifact); if (artifact.getFile() != null && !seen.add(fileName)) { duplicates.add(fileName); } } return duplicates; } private boolean isUnpackRequired(Artifact artifact) { if (this.unpacks != null) { for (Dependency unpack : this.unpacks) { if (artifact.getGroupId().equals(unpack.getGroupId()) && artifact.getArtifactId().equals(unpack.getArtifactId())) { return true; } } } return false; } private String getFileName(Artifact artifact) { StringBuilder sb = new StringBuilder(); sb.append(artifact.getArtifactId()).append("-").append(artifact.getBaseVersion()); String classifier = artifact.getClassifier(); if (classifier != null) { sb.append("-").append(classifier); } sb.append(".").append(artifact.getArtifactHandler().getExtension()); return sb.toString(); } } ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/main/java/com/alipay/sofa/ark/boot/mojo/MavenUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.tools.ArtifactItem; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.Dependency; import org.apache.maven.model.Parent; import org.apache.maven.project.MavenProject; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import static com.alipay.sofa.ark.spi.constant.Constants.STRING_COLON; import static java.util.Arrays.asList; public class MavenUtils { public static boolean isRootProject(MavenProject project) { if (project == null) { return true; } if (project.hasParent() && project.getParent().getBasedir() != null) { return false; } return true; } public static MavenProject getRootProject(MavenProject project) { if (project == null) { return null; } MavenProject parent = project; while (parent.hasParent() && parent.getParent().getBasedir() != null) { parent = parent.getParent(); } return parent; } /** * @param depTreeContent * @return */ public static Set convert(String depTreeContent) { Set artifactItems = new HashSet<>(); String[] contents = depTreeContent.split("\n"); for (String content : contents) { ArtifactItem artifactItem = getArtifactItem(content); if (artifactItem != null && !"test".equals(artifactItem.getScope())) { artifactItems.add(artifactItem); } } return artifactItems; } private static ArtifactItem getArtifactItem(String lineContent) { if (StringUtils.isEmpty(lineContent)) { return null; } lineContent = StringUtils.removeCR(lineContent); String[] contentInfos = lineContent.split(" "); if (contentInfos.length == 0) { return null; } Optional artifactStrOp = Arrays.stream(contentInfos).filter(c -> c.contains(":")).findFirst(); if (!artifactStrOp.isPresent()) { return null; } String[] artifactInfos = artifactStrOp.get().split(":"); ArtifactItem artifactItem = new ArtifactItem(); if (artifactInfos.length == 5) { // like "com.alipay.sofa:healthcheck-sofa-boot-starter:jar:3.11.1:provided" artifactItem.setGroupId(artifactInfos[0]); artifactItem.setArtifactId(artifactInfos[1]); artifactItem.setType(artifactInfos[2]); artifactItem.setVersion(artifactInfos[3]); artifactItem.setScope(artifactInfos[4]); } else if (artifactInfos.length == 6) { // like "io.sofastack:dynamic-stock-mng:jar:ark-biz:1.0.0:compile" artifactItem.setGroupId(artifactInfos[0]); artifactItem.setArtifactId(artifactInfos[1]); artifactItem.setType(artifactInfos[2]); artifactItem.setClassifier(artifactInfos[3]); artifactItem.setVersion(artifactInfos[4]); artifactItem.setScope(artifactInfos[5]); } else { return null; } return artifactItem; } private static List UN_LOG_SCOPES = asList("provided", "test", "import", "system"); public static boolean inUnLogScopes(String scope) { return UN_LOG_SCOPES.contains(scope); } public static String getGAVIdentity(Artifact artifact) { return artifact.getGroupId() + STRING_COLON + artifact.getArtifactId() + STRING_COLON + artifact.getBaseVersion(); } public static String getArtifactIdentity(Artifact artifact) { if (artifact.hasClassifier()) { return artifact.getGroupId() + STRING_COLON + artifact.getArtifactId() + STRING_COLON + artifact.getBaseVersion() + STRING_COLON + artifact.getClassifier() + STRING_COLON + artifact.getType(); } else { return artifact.getGroupId() + STRING_COLON + artifact.getArtifactId() + STRING_COLON + artifact.getBaseVersion() + STRING_COLON + artifact.getType(); } } public static String getArtifactIdentityWithoutVersion(Artifact artifact) { if (artifact.hasClassifier()) { return artifact.getGroupId() + STRING_COLON + artifact.getArtifactId() + STRING_COLON + artifact.getClassifier() + STRING_COLON + artifact.getType(); } else { return artifact.getGroupId() + STRING_COLON + artifact.getArtifactId() + STRING_COLON + artifact.getType(); } } public static String getDependencyIdentity(Dependency dependency) { if (org.apache.commons.lang3.StringUtils.isNotEmpty(dependency.getClassifier())) { return dependency.getGroupId() + STRING_COLON + dependency.getArtifactId() + STRING_COLON + dependency.getVersion() + STRING_COLON + dependency.getClassifier() + STRING_COLON + dependency.getType(); } else { return dependency.getGroupId() + STRING_COLON + dependency.getArtifactId() + STRING_COLON + dependency.getVersion() + STRING_COLON + dependency.getType(); } } public static String getDependencyIdentityWithoutVersion(Dependency dependency) { if (org.apache.commons.lang3.StringUtils.isNotEmpty(dependency.getClassifier())) { return dependency.getGroupId() + STRING_COLON + dependency.getArtifactId() + STRING_COLON + dependency.getClassifier() + STRING_COLON + dependency.getType(); } else { return dependency.getGroupId() + STRING_COLON + dependency.getArtifactId() + STRING_COLON + dependency.getType(); } } public static String getGAIdentity(Artifact artifact) { return artifact.getGroupId() + STRING_COLON + artifact.getArtifactId(); } public static String getGAIdentity(Parent parent) { return parent.getGroupId() + STRING_COLON + parent.getArtifactId(); } public static String getGAVIdentity(Parent parent) { return parent.getGroupId() + STRING_COLON + parent.getArtifactId() + STRING_COLON + parent.getVersion(); } } ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/main/java/com/alipay/sofa/ark/boot/mojo/ModuleSlimConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import java.util.LinkedHashSet; /** * @author lianglipeng.llp@alibaba-inc.com * @version $Id: ModuleSlimConfig.java, v 0.1 2024年07月12日 16:28 立蓬 Exp $ */ public class ModuleSlimConfig { private String packExcludesConfig; private String packExcludesUrl; /** * Colon separated groupId, artifactId [and classifier] to exclude (exact match). e.g: * group-a:tracer-core:3.0.10 * group-b:tracer-core:3.0.10:jdk17 */ private LinkedHashSet excludes = new LinkedHashSet<>(); /** * list of groupId names to exclude (exact match). */ private LinkedHashSet excludeGroupIds = new LinkedHashSet<>(); /** * list of artifact names to exclude (exact match). */ private LinkedHashSet excludeArtifactIds = new LinkedHashSet<>(); /** * Colon separated groupId, artifactId [and classifier] to exclude (exact match). e.g: * group-a:tracer-core:3.0.10 * group-b:tracer-core:3.0.10:jdk17 */ private LinkedHashSet includes = new LinkedHashSet<>(); /** * list of groupId names to exclude (exact match). */ private LinkedHashSet includeGroupIds = new LinkedHashSet<>(); /** * list of artifact names to exclude (exact match). */ private LinkedHashSet includeArtifactIds = new LinkedHashSet<>(); /** * 基座依赖标识,以 ${groupId}:${artifactId}:${version} 标识 */ private String baseDependencyParentIdentity; /** * 在排除依赖时,是否同时排除依赖及间接依赖。如:A依赖B,B依赖C,当 excludes 只配置了 A 时,B 和 C 都会被排除 */ private boolean excludeWithIndirectDependencies = true; /** * 在排除依赖时,如果排除的依赖与基座不一致,是否构建失败 */ private boolean buildFailWhenExcludeBaseDependencyWithDiffVersion = false; public LinkedHashSet getExcludeArtifactIds() { return excludeArtifactIds; } public LinkedHashSet getExcludeGroupIds() { return excludeGroupIds; } public LinkedHashSet getExcludes() { return excludes; } public String getPackExcludesConfig() { return packExcludesConfig; } public ModuleSlimConfig setPackExcludesConfig(String packExcludesConfig) { this.packExcludesConfig = packExcludesConfig; return this; } public String getPackExcludesUrl() { return packExcludesUrl; } public ModuleSlimConfig setPackExcludesUrl(String packExcludesUrl) { this.packExcludesUrl = packExcludesUrl; return this; } public String getBaseDependencyParentIdentity() { return baseDependencyParentIdentity; } public ModuleSlimConfig setBaseDependencyParentIdentity(String baseDependencyParentIdentity) { this.baseDependencyParentIdentity = baseDependencyParentIdentity; return this; } public ModuleSlimConfig setExcludes(LinkedHashSet excludes) { this.excludes = excludes; return this; } public ModuleSlimConfig setExcludeGroupIds(LinkedHashSet excludeGroupIds) { this.excludeGroupIds = excludeGroupIds; return this; } public ModuleSlimConfig setExcludeArtifactIds(LinkedHashSet excludeArtifactIds) { this.excludeArtifactIds = excludeArtifactIds; return this; } public LinkedHashSet getIncludes() { return includes; } public ModuleSlimConfig setIncludes(LinkedHashSet includes) { this.includes = includes; return this; } public LinkedHashSet getIncludeGroupIds() { return includeGroupIds; } public ModuleSlimConfig setIncludeGroupIds(LinkedHashSet includeGroupIds) { this.includeGroupIds = includeGroupIds; return this; } public LinkedHashSet getIncludeArtifactIds() { return includeArtifactIds; } public ModuleSlimConfig setIncludeArtifactIds(LinkedHashSet includeArtifactIds) { this.includeArtifactIds = includeArtifactIds; return this; } public boolean isExcludeWithIndirectDependencies() { return excludeWithIndirectDependencies; } public void setExcludeWithIndirectDependencies(boolean excludeWithIndirectDependencies) { this.excludeWithIndirectDependencies = excludeWithIndirectDependencies; } public boolean isBuildFailWhenExcludeBaseDependencyWithDiffVersion() { return buildFailWhenExcludeBaseDependencyWithDiffVersion; } public void setBuildFailWhenExcludeBaseDependencyWithDiffVersion(boolean buildFailWhenExcludeBaseDependencyWithDiffVersion) { this.buildFailWhenExcludeBaseDependencyWithDiffVersion = buildFailWhenExcludeBaseDependencyWithDiffVersion; } } ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/main/java/com/alipay/sofa/ark/boot/mojo/ModuleSlimExecutor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import com.alipay.sofa.ark.boot.mojo.model.ArkConfigHolder; import com.alipay.sofa.ark.common.util.ParseUtils; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.tools.ArtifactItem; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.Dependency; import org.apache.maven.model.DependencyManagement; import org.apache.maven.model.Model; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.apache.maven.project.ProjectBuilder; import org.apache.maven.project.ProjectBuildingException; import org.apache.maven.repository.RepositorySystem; import org.apache.maven.shared.dependency.graph.DependencyNode; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; import static com.alipay.sofa.ark.boot.mojo.MavenUtils.getArtifactIdentityWithoutVersion; import static com.alipay.sofa.ark.boot.mojo.MavenUtils.inUnLogScopes; import static com.alipay.sofa.ark.boot.mojo.utils.ParseUtils.getBooleanWithDefault; import static com.alipay.sofa.ark.boot.mojo.utils.ParseUtils.getStringSet; import static com.alipay.sofa.ark.spi.constant.Constants.ARK_CONF_BASE_DIR; import static com.alipay.sofa.ark.spi.constant.Constants.EXTENSION_EXCLUDES; import static com.alipay.sofa.ark.spi.constant.Constants.EXTENSION_EXCLUDES_ARTIFACTIDS; import static com.alipay.sofa.ark.spi.constant.Constants.EXTENSION_EXCLUDES_GROUPIDS; import static com.alipay.sofa.ark.spi.constant.Constants.EXTENSION_INCLUDES; import static com.alipay.sofa.ark.spi.constant.Constants.EXTENSION_INCLUDES_ARTIFACTIDS; import static com.alipay.sofa.ark.spi.constant.Constants.EXTENSION_INCLUDES_GROUPIDS; /** * @author lianglipeng.llp@alibaba-inc.com * @version $Id: ModuleSlimStrategy.java, v 0.1 2024年07月12日 16:21 立蓬 Exp $ */ public class ModuleSlimExecutor { private MavenProject project; private RepositorySystem repositorySystem; private ProjectBuilder projectBuilder; private DependencyNode projDependencyGraph; private ModuleSlimConfig config; private Log log; private File baseDir; private File sofaArkLogDirectory; private static final String EXTENSION_EXCLUDE_WITH_INDIRECT_DEPENDENCIES = "excludeWithIndirectDependencies"; private static final String EXTENSION_BUILD_FAIL_WHEN_EXCLUDE_DIFF_BASE_DEPENDENCY = "buildFailWhenExcludeDiffBaseDependency"; private static final String DEFAULT_EXCLUDE_RULES = "rules.txt"; ModuleSlimExecutor(MavenProject project, RepositorySystem repositorySystem, ProjectBuilder projectBuilder, DependencyNode projDependencyGraph, ModuleSlimConfig config, File baseDir, File sofaArkLogDirectory, Log log) { this.project = project; this.repositorySystem = repositorySystem; this.projectBuilder = projectBuilder; this.projDependencyGraph = projDependencyGraph; this.config = config; this.baseDir = baseDir; this.sofaArkLogDirectory = sofaArkLogDirectory; this.log = log; } public Set getSlimmedArtifacts() throws MojoExecutionException, IOException { initSlimStrategyConfig(); Set toFilterByBase = getArtifactsToFilterByParentIdentity(project.getArtifacts()); Set toFilterByBasePlugin = getArtifactsToFilterByBasePlugin(project .getArtifacts()); Set toFilterByExclude = getArtifactsToFilterByExcludeConfig(project .getArtifacts()); Set toAddByInclude = getArtifactsToAddByIncludeConfig(project.getArtifacts()); Set toFilter = new HashSet<>(); toFilter.addAll(toFilterByBase); toFilter.addAll(toFilterByBasePlugin); toFilter.addAll(toFilterByExclude); toFilter.removeAll(toAddByInclude); checkExcludeByParentIdentity(toFilter); Set filteredArtifacts = new HashSet<>(project.getArtifacts()); Set excludedArtifacts = new LinkedHashSet<>(); filteredArtifacts.stream().filter(toFilter::contains).forEach( artifact -> { excludedArtifacts.add(getArtifactIdentityWithoutVersion(artifact)); } ); filteredArtifacts.removeAll(toFilter); Set compiledArtifacts = new LinkedHashSet<>(); toAddByInclude.stream() .filter(artifact -> Artifact.SCOPE_PROVIDED.equals(artifact.getScope())) .forEach(artifact -> { compiledArtifacts.add(getArtifactIdentityWithoutVersion(artifact)); artifact.setScope(Artifact.SCOPE_COMPILE); }); saveModuleSlimResult(excludedArtifacts, compiledArtifacts); return filteredArtifacts; } protected Model resolvePomAsOriginalModel(String groupId, String artifactId, String version) { try { Artifact artifact = repositorySystem .createProjectArtifact(groupId, artifactId, version); return projectBuilder.build(artifact, project.getProjectBuildingRequest()).getProject() .getOriginalModel(); } catch (ProjectBuildingException e) { log.warn("resolve pom as project error: with " + groupId + ":" + artifactId + ":" + version); return null; } } protected Set getArtifactsToFilterByBasePlugin(Set artifacts) { if (!excludeWithBaseDependencyParentIdentity()) { return Collections.emptySet(); } return getSameArtifactsInBasePlugin(artifacts); } private Set getSameArtifactsInBasePlugin(Set artifacts) { List basePluginModels = getBasePluginModel(); List dependenciesInBasePlugin = basePluginModels.stream().flatMap(it->Optional.ofNullable(it.getDependencyManagement()).orElseGet( DependencyManagement::new).getDependencies().stream()).collect(Collectors.toList()); // 如果 artifacts 中含有 base Plugin 里配置的 dependencyManagement 的 dependencies,那么需要过滤 Set dependencyIdentities = dependenciesInBasePlugin.stream().map(MavenUtils::getDependencyIdentityWithoutVersion).collect(Collectors.toSet()); return artifacts.stream().filter(it -> dependencyIdentities.contains(getArtifactIdentityWithoutVersion(it))).collect(Collectors.toSet()); } protected List getBasePluginModel(){ // 先通过 project.getOriginalModel().getDependencyManagement().getDependencies() 获取所有 type 为 pom 的 dependencies List pomDependenciesInDependencyManagement = Optional.ofNullable(project) .map(MavenProject::getOriginalModel) .map(Model::getDependencyManagement) .map(DependencyManagement::getDependencies) .orElse(Collections.emptyList()) .stream() .filter(it -> StringUtils.equals("pom",it.getType()) && StringUtils.equals("import",it.getScope())) .collect(Collectors.toList()); // 然后解析这些 dependencies 为 artifacts,并转为 mavenProject, 获取其 model List basePluginModels = new ArrayList<>(); for (Dependency dependency : pomDependenciesInDependencyManagement) { // 解析 dependency 为 model Model model = resolvePomAsOriginalModel(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion()); // 如果 model 的 parent 和我们的 baseVersion 一致,那么这个 model 就是 basePlugin if (null != model && null != model.getParent() && (StringUtils.equals(config.getBaseDependencyParentIdentity(),MavenUtils.getGAIdentity(model.getParent())) || StringUtils.equals(config.getBaseDependencyParentIdentity(),MavenUtils.getGAVIdentity(model.getParent()))) ){ basePluginModels.add(model); } } return basePluginModels; } protected void checkExcludeByParentIdentity(Set toFilter) throws MojoExecutionException { if (StringUtils.isEmpty(config.getBaseDependencyParentIdentity())) { return; } Set excludedButNoDependencyInBase = getExcludedButNoDependencyInBase(toFilter); Set excludedButDifferentVersionDependencyInBase = getExcludedButDifferentVersionDependencyInBase(toFilter); if(excludedButNoDependencyInBase.isEmpty() && excludedButDifferentVersionDependencyInBase.isEmpty()){ getLog().info(String.format("check excludeWithBaseDependencyParentIdentity success with base: %s",config.getBaseDependencyParentIdentity())); return; } // Dependency not found in base; please add it to the base or do not exclude it in the module excludedButNoDependencyInBase.forEach(artifact -> { getLog().error( String.format( "error to exclude package jar: %s because no such jar in base, please keep the jar or add it to base", MavenUtils.getArtifactIdentity(artifact))); }); // The base contains this dependency, but the version and module are inconsistent; Please use the same dependency version as the base in the module. List baseDependencies = getAllBaseDependencies(); Map baseDependencyIdentityWithoutVersion = baseDependencies.stream().collect(Collectors.toMap(MavenUtils::getDependencyIdentityWithoutVersion, it -> it)); excludedButDifferentVersionDependencyInBase.forEach(artifact -> { Dependency baseDependency = baseDependencyIdentityWithoutVersion.get(getArtifactIdentityWithoutVersion(artifact)); getLog().error( String.format( "error to exclude package jar: %s because it has different version with: %s in base, please keep the jar or set same version with base", MavenUtils.getArtifactIdentity(artifact), MavenUtils.getDependencyIdentity(baseDependency))); }); if(config.isBuildFailWhenExcludeBaseDependencyWithDiffVersion()){ throw new MojoExecutionException(String.format("check excludeWithBaseDependencyParentIdentity failed with base: %s",config.getBaseDependencyParentIdentity())); } } protected Set getArtifactsToFilterByParentIdentity(Set artifacts) throws MojoExecutionException { if (!excludeWithBaseDependencyParentIdentity()) { return Collections.emptySet(); } // 过滤出模块和基座版本一致的依赖,即需要瘦身的依赖 return getSameArtifactsWithBase(artifacts); } private boolean excludeWithBaseDependencyParentIdentity() { return StringUtils.isNotEmpty(config.getBaseDependencyParentIdentity()); } private Set getSameArtifactsWithBase(Set artifacts) throws MojoExecutionException { List baseDependencies = getAllBaseDependencies(); Set dependencyIdentities = baseDependencies.stream().map(MavenUtils::getDependencyIdentityWithoutVersion).collect(Collectors.toSet()); return artifacts.stream().filter(it -> dependencyIdentities.contains(getArtifactIdentityWithoutVersion(it))).collect(Collectors.toSet()); } private Set getExcludedButNoDependencyInBase(Set toFilter) throws MojoExecutionException { List baseDependencies = getAllBaseDependencies(); Map baseDependencyIdentityWithoutVersion = baseDependencies.stream().collect(Collectors.toMap(MavenUtils::getDependencyIdentityWithoutVersion, it -> it)); return toFilter.stream().filter(it -> !baseDependencyIdentityWithoutVersion.containsKey(getArtifactIdentityWithoutVersion(it))).collect(Collectors.toSet()); } private Set getExcludedButDifferentVersionDependencyInBase(Set toFilter) throws MojoExecutionException { List baseDependencies = getAllBaseDependencies(); Map baseDependencyIdentityWithoutVersion = baseDependencies.stream().collect(Collectors.toMap(MavenUtils::getDependencyIdentityWithoutVersion, it -> it)); return toFilter.stream().filter(artifact -> { String identityWithoutVersion = getArtifactIdentityWithoutVersion(artifact); return baseDependencyIdentityWithoutVersion.containsKey(identityWithoutVersion) && (!artifact.getBaseVersion().equals(baseDependencyIdentityWithoutVersion.get(identityWithoutVersion).getVersion())); } ).collect(Collectors.toSet()); } private List getAllBaseDependencies() throws MojoExecutionException { // 获取基座DependencyParent的原始Model Model baseDependencyPom = getBaseDependencyParentOriginalModel(); if (null == baseDependencyPom) { throw new MojoExecutionException( String.format("can not find base dependency parent: %s", config.getBaseDependencyParentIdentity())); } if (null == baseDependencyPom.getDependencyManagement()) { return Collections.emptyList(); } return baseDependencyPom.getDependencyManagement().getDependencies(); } protected Model getBaseDependencyParentOriginalModel() { MavenProject proj = project; while (null != proj) { if (MavenUtils.getGAIdentity(proj.getArtifact()).equals( config.getBaseDependencyParentIdentity()) || MavenUtils.getGAVIdentity(proj.getArtifact()).equals( config.getBaseDependencyParentIdentity())) { return proj.getOriginalModel(); } proj = proj.getParent(); } return null; } protected void initSlimStrategyConfig() throws IOException { Map arkYaml = ArkConfigHolder.getArkYaml(baseDir.getAbsolutePath()); Properties prop = ArkConfigHolder.getArkProperties(baseDir.getAbsolutePath()); config.setExcludeWithIndirectDependencies(getBooleanWithDefault(prop, arkYaml, EXTENSION_EXCLUDE_WITH_INDIRECT_DEPENDENCIES, true)); config.setBuildFailWhenExcludeBaseDependencyWithDiffVersion(getBooleanWithDefault(prop, arkYaml, EXTENSION_BUILD_FAIL_WHEN_EXCLUDE_DIFF_BASE_DEPENDENCY, false)); initExcludeAndIncludeConfig(); } /** * exclude and include config comes from 3 parts: * 1. extension from config file that configured by user in sofa-ark-maven-plugin * 2. extension from default bootstrap.properties or bootstrap.yml * 3. extension from url * @throws IOException */ protected void initExcludeAndIncludeConfig() throws IOException { // extension from other resource if (!StringUtils.isEmpty(config.getPackExcludesConfig())) { extensionExcludeAndIncludeArtifacts(baseDir + File.separator + ARK_CONF_BASE_DIR + File.separator + config.getPackExcludesConfig()); } else { extensionExcludeAndIncludeArtifacts(baseDir + File.separator + ARK_CONF_BASE_DIR + File.separator + DEFAULT_EXCLUDE_RULES); } // extension from default bootstrap.properties or bootstrap.yml configExcludeArtifactsByDefault(); // extension from url if (StringUtils.isNotBlank(config.getPackExcludesUrl())) { extensionExcludeArtifactsFromUrl(config.getPackExcludesUrl(), project.getArtifacts()); } } protected Set getArtifactsToFilterByExcludeConfig(Set artifacts) { Set literalArtifactsToExclude = getLiteralArtifactsToFilterByExcludeConfig(artifacts); if (config.isExcludeWithIndirectDependencies()) { return excludeWithIndirectDependencies(literalArtifactsToExclude, artifacts); } return literalArtifactsToExclude; } private Set excludeWithIndirectDependencies(Set literalArtifactsToExclude, Set artifacts) { Set excludeArtifactIdentities = literalArtifactsToExclude.stream().map(MavenUtils::getArtifactIdentity).collect(Collectors.toSet()); Map artifactMap = artifacts.stream().collect(Collectors.toMap(MavenUtils::getArtifactIdentity,it->it)); return getExcludeWithIndirectDependencies(projDependencyGraph,excludeArtifactIdentities,artifactMap); } private Set getExcludeWithIndirectDependencies(DependencyNode node, Set literalArtifactsToExclude, Map artifacts) { if (null == node) { return Collections.emptySet(); } Set result = new LinkedHashSet<>(); String artifactIdentity = MavenUtils.getArtifactIdentity(node.getArtifact()); if (literalArtifactsToExclude.contains(artifactIdentity)) { // 排除当前依赖 result.add(artifacts.get(artifactIdentity)); // 排除当前依赖的所有依赖 result.addAll(getAllDependencies(node, artifacts)); return result; } for (DependencyNode child : node.getChildren()) { result.addAll(getExcludeWithIndirectDependencies(child, literalArtifactsToExclude, artifacts)); } return result; } private Set getAllDependencies(DependencyNode node, Map artifacts) { if (null == node) { return Collections.emptySet(); } Set result = new HashSet<>(); for (DependencyNode child : node.getChildren()) { String artifactId = MavenUtils.getArtifactIdentity(child.getArtifact()); if (artifacts.containsKey(artifactId)) { result.add(artifacts.get(artifactId)); result.addAll(getAllDependencies(child, artifacts)); } } return result; } protected Set getLiteralArtifactsToFilterByExcludeConfig(Set artifacts) { List excludeList = new ArrayList<>(); if (config != null && (config.getExcludes().contains("*") || config.getExcludes().contains(".*"))) { return artifacts; } for (String exclude : config.getExcludes()) { ArtifactItem item = ArtifactItem.parseArtifactItem(exclude); excludeList.add(item); } Set result = new LinkedHashSet<>(); for (Artifact e : artifacts) { if (checkMatchExclude(excludeList, e)) { result.add(e); } } return result; } protected Set getArtifactsToAddByIncludeConfig(Set artifacts) { List includeList = new ArrayList<>(); if (config != null && (config.getIncludes().contains("*") || config.getIncludes().contains(".*"))) { return artifacts; } for (String include : config.getIncludes()) { ArtifactItem item = ArtifactItem.parseArtifactItem(include); includeList.add(item); } Set result = new LinkedHashSet<>(); for (Artifact e : artifacts) { if (checkMatchInclude(includeList, e)) { result.add(e); } } return result; } /** * This method is core method for excluding artifacts in sofa-ark-maven-plugin <excludeGroupIds> * and <excludeArtifactIds> config. * * @param excludeList * @param artifact * @return */ private boolean checkMatchExclude(List excludeList, Artifact artifact) { for (ArtifactItem exclude : excludeList) { if (exclude.isSameWithVersion(ArtifactItem.parseArtifactItem(artifact))) { return true; } } if (checkMatchGroupId(config.getExcludeGroupIds(), artifact)) { return true; } return checkMatchArtifactId(config.getExcludeArtifactIds(), artifact); } /** * This method is core method for including artifacts in sofa-ark-maven-plugin <includeGroupIds> * and <includeArtifactIds> config. * * @param includeList * @param artifact * @return */ private boolean checkMatchInclude(List includeList, Artifact artifact) { for (ArtifactItem include : includeList) { if (include.isSameWithVersion(ArtifactItem.parseArtifactItem(artifact))) { return true; } } if (checkMatchGroupId(config.getIncludeGroupIds(), artifact)) { return true; } return checkMatchArtifactId(config.getIncludeArtifactIds(), artifact); } private boolean checkMatchGroupId(Set groupIds, Artifact artifact) { if (groupIds != null) { // 支持通配符 for (String groupId : groupIds) { if (groupId.endsWith(Constants.PACKAGE_PREFIX_MARK) || groupId.endsWith(Constants.PACKAGE_PREFIX_MARK_2)) { if (groupId.endsWith(Constants.PACKAGE_PREFIX_MARK_2)) { groupId = StringUtils.removeEnd(groupId, Constants.PACKAGE_PREFIX_MARK_2); } else if (groupId.endsWith(Constants.PACKAGE_PREFIX_MARK)) { groupId = StringUtils.removeEnd(groupId, Constants.PACKAGE_PREFIX_MARK); } if (artifact.getGroupId().startsWith(groupId)) { return true; } } else { if (artifact.getGroupId().equals(groupId)) { return true; } } } } return false; } private boolean checkMatchArtifactId(Set artifactIds, Artifact artifact) { if (artifactIds != null) { // 支持通配符 for (String artifactId : artifactIds) { if (artifactId.endsWith(Constants.PACKAGE_PREFIX_MARK) || artifactId.endsWith(Constants.PACKAGE_PREFIX_MARK_2)) { if (artifactId.endsWith(Constants.PACKAGE_PREFIX_MARK_2)) { artifactId = StringUtils.removeEnd(artifactId, Constants.PACKAGE_PREFIX_MARK_2); } else if (artifactId.endsWith(Constants.PACKAGE_PREFIX_MARK)) { artifactId = StringUtils.removeEnd(artifactId, Constants.PACKAGE_PREFIX_MARK); } if (artifact.getArtifactId().startsWith(artifactId)) { return true; } } else { if (artifact.getArtifactId().equals(artifactId)) { return true; } } } } return false; } protected void extensionExcludeAndIncludeArtifacts(String extraResources) { try { File configFile = com.alipay.sofa.ark.common.util.FileUtils.file(extraResources); if (configFile.exists()) { BufferedReader bufferedReader = new BufferedReader(new FileReader(configFile)); String dataLine; while ((dataLine = bufferedReader.readLine()) != null) { if (dataLine.startsWith(EXTENSION_EXCLUDES)) { ParseUtils.parseExcludeConf(config.getExcludes(), dataLine, EXTENSION_EXCLUDES); } else if (dataLine.startsWith(EXTENSION_EXCLUDES_GROUPIDS)) { ParseUtils.parseExcludeConf(config.getExcludeGroupIds(), dataLine, EXTENSION_EXCLUDES_GROUPIDS); } else if (dataLine.startsWith(EXTENSION_EXCLUDES_ARTIFACTIDS)) { ParseUtils.parseExcludeConf(config.getExcludeArtifactIds(), dataLine, EXTENSION_EXCLUDES_ARTIFACTIDS); } else if (dataLine.startsWith(EXTENSION_INCLUDES)) { ParseUtils.parseExcludeConf(config.getIncludes(), dataLine, EXTENSION_INCLUDES); } else if (dataLine.startsWith(EXTENSION_INCLUDES_GROUPIDS)) { ParseUtils.parseExcludeConf(config.getIncludeGroupIds(), dataLine, EXTENSION_INCLUDES_GROUPIDS); } else if (dataLine.startsWith(EXTENSION_INCLUDES_ARTIFACTIDS)) { ParseUtils.parseExcludeConf(config.getIncludeArtifactIds(), dataLine, EXTENSION_INCLUDES_ARTIFACTIDS); } } } } catch (IOException ex) { getLog().error("failed to extension excludes artifacts.", ex); } } protected void configExcludeArtifactsByDefault() throws IOException { // extension from default ark.properties and ark.yml Map arkYaml = ArkConfigHolder.getArkYaml(baseDir.getAbsolutePath()); Properties prop = ArkConfigHolder.getArkProperties(baseDir.getAbsolutePath()); config.getExcludes().addAll(getStringSet(prop, EXTENSION_EXCLUDES)); config.getExcludeGroupIds().addAll(getStringSet(prop, EXTENSION_EXCLUDES_GROUPIDS)); config.getExcludeArtifactIds().addAll(getStringSet(prop, EXTENSION_EXCLUDES_ARTIFACTIDS)); config.getIncludes().addAll(getStringSet(prop, EXTENSION_INCLUDES)); config.getIncludeGroupIds().addAll(getStringSet(prop, EXTENSION_INCLUDES_GROUPIDS)); config.getIncludeArtifactIds().addAll(getStringSet(prop, EXTENSION_INCLUDES_ARTIFACTIDS)); config.getExcludes().addAll(getStringSet(arkYaml, EXTENSION_EXCLUDES)); config.getExcludeGroupIds().addAll(getStringSet(arkYaml, EXTENSION_EXCLUDES_GROUPIDS)); config.getExcludeArtifactIds() .addAll(getStringSet(arkYaml, EXTENSION_EXCLUDES_ARTIFACTIDS)); config.getIncludes().addAll(getStringSet(arkYaml, EXTENSION_INCLUDES)); config.getIncludeGroupIds().addAll(getStringSet(arkYaml, EXTENSION_INCLUDES_GROUPIDS)); config.getIncludeArtifactIds() .addAll(getStringSet(arkYaml, EXTENSION_INCLUDES_ARTIFACTIDS)); } protected void extensionExcludeArtifactsFromUrl(String packExcludesUrl, Set artifacts) { try { CloseableHttpClient client = HttpClients.createDefault(); HttpGet request = new HttpGet(packExcludesUrl); CloseableHttpResponse response = client.execute(request); int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200 && response.getEntity() != null) { String result = EntityUtils.toString(response.getEntity()); getLog().info( String.format("success to get excludes config from url: %s, response: %s", packExcludesUrl, result)); ObjectMapper objectMapper = new ObjectMapper(); ExcludeConfigResponse excludeConfigResponse = objectMapper.readValue(result, ExcludeConfigResponse.class); if (excludeConfigResponse.isSuccess() && excludeConfigResponse.getResult() != null) { ExcludeConfig excludeConfig = excludeConfigResponse.getResult(); List jarBlackGroupIds = excludeConfig.getJarBlackGroupIds(); List jarBlackArtifactIds = excludeConfig.getJarBlackArtifactIds(); List jarBlackList = excludeConfig.getJarBlackList(); List jarWhiteGroupIds = excludeConfig.getJarWhiteGroupIds(); List jarWhiteArtifactIds = excludeConfig.getJarWhiteArtifactIds(); List jarWhiteList = excludeConfig.getJarWhiteList(); if (CollectionUtils.isNotEmpty(jarBlackGroupIds)) { config.getExcludeGroupIds().addAll(jarBlackGroupIds); } if (CollectionUtils.isNotEmpty(jarBlackArtifactIds)) { config.getExcludeArtifactIds().addAll(jarBlackArtifactIds); } if (CollectionUtils.isNotEmpty(jarBlackList)) { config.getExcludes().addAll(jarBlackList); } if (CollectionUtils.isNotEmpty(jarWhiteGroupIds)) { config.getIncludeGroupIds().addAll(jarWhiteGroupIds); } if (CollectionUtils.isNotEmpty(jarWhiteArtifactIds)) { config.getIncludeArtifactIds().addAll(jarWhiteArtifactIds); } if (CollectionUtils.isNotEmpty(jarWhiteList)) { config.getIncludes().addAll(jarWhiteList); } logExcludeMessage(jarBlackGroupIds, jarBlackArtifactIds, jarBlackList, artifacts, true); List jarWarnGroupIds = excludeConfig.getJarWarnGroupIds(); List jarWarnArtifactIds = excludeConfig.getJarWarnArtifactIds(); List jarWarnList = excludeConfig.getJarWarnList(); logExcludeMessage(jarWarnGroupIds, jarWarnArtifactIds, jarWarnList, artifacts, false); } } response.close(); client.close(); } catch (Exception e) { getLog().error( String.format("failed to get excludes config from url: %s", packExcludesUrl), e); } } protected void logExcludeMessage(List jarGroupIds, List jarArtifactIds, List jarList, Set artifacts, boolean error) { if (CollectionUtils.isNotEmpty(jarGroupIds)) { for (Artifact artifact : artifacts) { if (inUnLogScopes(artifact.getScope())) { continue; } for (String jarBlackGroupId : jarGroupIds) { if (jarBlackGroupId.endsWith(Constants.PACKAGE_PREFIX_MARK) || jarBlackGroupId.endsWith(Constants.PACKAGE_PREFIX_MARK_2)) { if (jarBlackGroupId.endsWith(Constants.PACKAGE_PREFIX_MARK_2)) { jarBlackGroupId = StringUtils.remove(jarBlackGroupId, Constants.PACKAGE_PREFIX_MARK_2); } else if (jarBlackGroupId.endsWith(Constants.PACKAGE_PREFIX_MARK)) { jarBlackGroupId = StringUtils.removeEnd(jarBlackGroupId, Constants.PACKAGE_PREFIX_MARK); } if (artifact.getGroupId().startsWith(jarBlackGroupId)) { if (error) { getLog() .error( String .format( "Error to package jar: %s due to match groupId: %s, automatically exclude it.", artifact, jarBlackGroupId)); } else { getLog().warn( String.format( "Warn to package jar: %s due to match groupId: %s", artifact, jarBlackGroupId)); } } } else { if (artifact.getGroupId().equals(jarBlackGroupId)) { if (error) { getLog() .error( String .format( "Error to package jar: %s due to match groupId: %s, automatically exclude it.", artifact, jarBlackGroupId)); } else { getLog().warn( String.format( "Warn to package jar: %s due to match groupId: %s", artifact, jarBlackGroupId)); } } } } } } if (CollectionUtils.isNotEmpty(jarArtifactIds)) { for (Artifact artifact : artifacts) { if (inUnLogScopes(artifact.getScope())) { continue; } for (String jarBlackArtifactId : jarArtifactIds) { if (jarBlackArtifactId.endsWith(Constants.PACKAGE_PREFIX_MARK) || jarBlackArtifactId.endsWith(Constants.PACKAGE_PREFIX_MARK_2)) { if (jarBlackArtifactId.endsWith(Constants.PACKAGE_PREFIX_MARK_2)) { jarBlackArtifactId = StringUtils.removeEnd(jarBlackArtifactId, Constants.PACKAGE_PREFIX_MARK_2); } else if (jarBlackArtifactId.endsWith(Constants.PACKAGE_PREFIX_MARK)) { jarBlackArtifactId = StringUtils.removeEnd(jarBlackArtifactId, Constants.PACKAGE_PREFIX_MARK); } if (artifact.getArtifactId().startsWith(jarBlackArtifactId)) { if (error) { getLog() .error( String .format( "Error to package jar: %s due to match artifactId: %s, automatically exclude it.", artifact, jarBlackArtifactId)); } else { getLog().warn( String.format( "Warn to package jar: %s due to match artifactId: %s", artifact, jarBlackArtifactId)); } } } else { if (artifact.getArtifactId().equals(jarBlackArtifactId)) { if (error) { getLog() .error( String .format( "Error to package jar: %s due to match artifactId: %s, automatically exclude it.", artifact, jarBlackArtifactId)); } else { getLog().warn( String.format( "Warn to package jar: %s due to match artifactId: %s", artifact, jarBlackArtifactId)); } } } } } } if (CollectionUtils.isNotEmpty(jarList)) { for (Artifact artifact : artifacts) { if (inUnLogScopes(artifact.getScope())) { continue; } for (String jarBlack : jarList) { if (jarBlack.equals(String.join(":", artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion()))) { if (error) { getLog() .error( String .format( "Error to package jar: %s due to match groupId:artifactId:version: %s, automatically exclude it.", artifact, jarBlack)); } else { getLog() .warn( String .format( "Warn to package jar: %s due to match groupId:artifactId:version: %s", artifact, jarBlack)); } } } } } } private Log getLog() { return log; } private void saveModuleSlimResult(Set excludedArtifacts, Set compiledArtifacts) { if (sofaArkLogDirectory == null) { log.warn("outputDirectory is null, skip saving module slim result"); return; } try { ModuleSlimResult result = new ModuleSlimResult(); result.setExcluded(new ArrayList<>(excludedArtifacts)); result.setCompiled(new ArrayList<>(compiledArtifacts)); File resultFile = new File(sofaArkLogDirectory, "module-slim-results.json"); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.writeValue(resultFile, result); } catch (Exception e) { log.error("Failed to save module slim result: " + e.getMessage(), e); } } public static class ExcludeConfigResponse { private boolean success; private ExcludeConfig result; public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public ExcludeConfig getResult() { return result; } public void setResult(ExcludeConfig result) { this.result = result; } } public static class ExcludeConfig { private String app; private List jarBlackGroupIds; private List jarBlackArtifactIds; private List jarBlackList; private List jarWhiteGroupIds; private List jarWhiteArtifactIds; private List jarWhiteList; private List jarWarnGroupIds; private List jarWarnArtifactIds; private List jarWarnList; public String getApp() { return app; } public void setApp(String app) { this.app = app; } public List getJarBlackGroupIds() { return jarBlackGroupIds; } public void setJarBlackGroupIds(List jarBlackGroupIds) { this.jarBlackGroupIds = jarBlackGroupIds; } public List getJarBlackArtifactIds() { return jarBlackArtifactIds; } public void setJarBlackArtifactIds(List jarBlackArtifactIds) { this.jarBlackArtifactIds = jarBlackArtifactIds; } public List getJarBlackList() { return jarBlackList; } public void setJarBlackList(List jarBlackList) { this.jarBlackList = jarBlackList; } public List getJarWhiteGroupIds() { return jarWhiteGroupIds; } public void setJarWhiteGroupIds(List jarWhiteGroupIds) { this.jarWhiteGroupIds = jarWhiteGroupIds; } public List getJarWhiteArtifactIds() { return jarWhiteArtifactIds; } public void setJarWhiteArtifactIds(List jarWhiteArtifactIds) { this.jarWhiteArtifactIds = jarWhiteArtifactIds; } public List getJarWhiteList() { return jarWhiteList; } public void setJarWhiteList(List jarWhiteList) { this.jarWhiteList = jarWhiteList; } public List getJarWarnGroupIds() { return jarWarnGroupIds; } public void setJarWarnGroupIds(List jarWarnGroupIds) { this.jarWarnGroupIds = jarWarnGroupIds; } public List getJarWarnArtifactIds() { return jarWarnArtifactIds; } public void setJarWarnArtifactIds(List jarWarnArtifactIds) { this.jarWarnArtifactIds = jarWarnArtifactIds; } public List getJarWarnList() { return jarWarnList; } public void setJarWarnList(List jarWarnList) { this.jarWarnList = jarWarnList; } } public static class ModuleSlimResult { private List excluded; private List compiled; public List getExcluded() { return excluded; } public void setExcluded(List excluded) { this.excluded = excluded; } public List getCompiled() { return compiled; } public void setCompiled(List compiled) { this.compiled = compiled; } } } ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/main/java/com/alipay/sofa/ark/boot/mojo/RepackageMojo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import com.alipay.sofa.ark.boot.mojo.model.ArkConfigHolder; import com.alipay.sofa.ark.tools.ArtifactItem; import com.alipay.sofa.ark.tools.Libraries; import com.alipay.sofa.ark.tools.Repackager; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.artifact.resolver.ArtifactResolutionRequest; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Dependency; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.plugins.dependency.tree.TreeMojo; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectHelper; import org.apache.maven.project.ProjectBuilder; import org.apache.maven.project.ProjectBuildingRequest; import org.apache.maven.repository.RepositorySystem; import org.apache.maven.shared.dependency.graph.DependencyNode; import org.apache.maven.shared.invoker.DefaultInvocationRequest; import org.apache.maven.shared.invoker.DefaultInvoker; import org.apache.maven.shared.invoker.InvocationRequest; import org.apache.maven.shared.invoker.InvocationResult; import org.apache.maven.shared.invoker.Invoker; import org.apache.maven.shared.invoker.MavenInvocationException; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import static com.alipay.sofa.ark.boot.mojo.utils.ParseUtils.getStringSet; import static com.alipay.sofa.ark.spi.constant.Constants.DECLARED_LIBRARIES_WHITELIST; import static com.alipay.sofa.ark.tools.ArtifactItem.parseArtifactItem; /** * Repackages existing JAR archives so that they can be executed from the command * line using {@literal java -jar}. * * @author qilong.zql * @since 0.1.0 */ @Mojo(name = "repackage", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME) public class RepackageMojo extends TreeMojo { private static final String BIZ_NAME = "com.alipay.sofa.ark.bizName"; private static final String DEFAULT_EXCLUDE_RULES = "rules.txt"; @Parameter(defaultValue = "${project}", readonly = true, required = true) private MavenProject mavenProject; @Component private MavenProjectHelper projectHelper; @Component private MavenSession mavenSession; @Component private ArtifactFactory artifactFactory; @Component private RepositorySystem repositorySystem; @Component private ProjectBuilder projectBuilder; /** * Directory containing the generated archive * @since 0.1.0 */ @Parameter(defaultValue = "${project.build.directory}", required = true) private File outputDirectory; @Parameter(defaultValue = "${project.basedir}", required = true) private File baseDir; /** * Deprecated by the default conf/ark/bootstrap.properties or conf/ark/bootstrap.yml */ @Deprecated @Parameter(defaultValue = "", required = false) private String packExcludesConfig; @Parameter(defaultValue = "", required = false) private String packExcludesUrl; /** * Name of the generated archive * @since 0.1.0 */ @Parameter(defaultValue = "${project.build.finalName}", required = true) private String finalName; /** * Skip the repackage goal. * @since 0.1.0 */ @Parameter(property = "sofa.ark.repackage.skip", defaultValue = "false") private boolean skip; /** * Classifier to add to the artifact generated. If attach is set 'true', the * artifact will be attached with that classifier. Attaching the artifact * allows to deploy it alongside to the main artifact. * @since 0.1.0 */ @Parameter(defaultValue = "ark-biz", readonly = true) private String bizClassifier; /** * */ @Parameter(defaultValue = "${project.artifactId}") private String bizName; /** * ark biz version * @since 0.4.0 */ @Parameter(defaultValue = "${project.version}") private String bizVersion; /** * ark biz version * @since 0.4.0 */ @Parameter(defaultValue = "100", property = "sofa.ark.biz.priority") protected Integer priority; /** * Classifier to add to the executable artifact generated, if needed, * 'sofa-ark' is recommended. * * @since 0.1.0 */ @Parameter(defaultValue = "ark-executable", readonly = true) private String arkClassifier; /** * Attach the module archive to be installed and deployed. * @since 0.1.0 */ @Parameter(defaultValue = "false") private boolean attach; /** * The name of the main class. If not specified the first compiled class found that * contains a 'main' method will be used. * @since 0.1.0 */ @Parameter private String mainClass; /** * A list of the libraries that must be unpacked from fat jars in order to run. * Specify each library as a <dependency> with a * <groupId> and a <artifactId> and they will be * unpacked at runtime. * @since 0.1.0 */ @Parameter private List requiresUnpack; /** * The version of SOFAArk, same with plugin version. when developer * want develop a application running on the SOFA-Ark. Just configure * sofa-ark-maven-plugin. */ private String arkVersion; /** * mvn command user properties */ private ProjectBuildingRequest projectBuildingRequest; /** * Colon separated groupId, artifactId [and classifier] to exclude (exact match). e.g: * group-a:tracer-core:3.0.10 * group-b:tracer-core:3.0.10:jdk17 */ @Parameter(defaultValue = "") private LinkedHashSet excludes = new LinkedHashSet<>(); /** * list of groupId names to exclude (exact match). */ @Parameter(defaultValue = "") private LinkedHashSet excludeGroupIds = new LinkedHashSet<>(); /** * list of artifact names to exclude (exact match). */ @Parameter(defaultValue = "") private LinkedHashSet excludeArtifactIds = new LinkedHashSet<>(); /** * Colon separated groupId, artifactId [and classifier] to include (exact match). e.g: * group-a:tracer-core:3.0.10 * group-b:tracer-core:3.0.10:jdk17 */ @Parameter(defaultValue = "") private LinkedHashSet includes = new LinkedHashSet<>(); /** * list of groupId names to include (exact match). */ @Parameter(defaultValue = "") private LinkedHashSet includeGroupIds = new LinkedHashSet<>(); /** * list of artifact names to include (exact match). */ @Parameter(defaultValue = "") private LinkedHashSet includeArtifactIds = new LinkedHashSet<>(); /** * list of packages denied to be imported */ @Parameter(defaultValue = "") private LinkedHashSet denyImportPackages; /** * list of classes denied to be imported */ @Parameter(defaultValue = "") private LinkedHashSet denyImportClasses; /** * list of resources denied to be imported */ @Parameter(defaultValue = "") private LinkedHashSet denyImportResources; /** * list of inject plugin dependencies */ @Parameter(defaultValue = "") private LinkedHashSet injectPluginDependencies = new LinkedHashSet<>(); /** * list of inject plugin export packages */ @Parameter(defaultValue = "") private LinkedHashSet injectPluginExportPackages = new LinkedHashSet<>(); /** * whether package provided dependencies into ark */ @Parameter(defaultValue = "false") private boolean packageProvided; /** * whether to skip package ark-executable jar */ @Parameter(defaultValue = "false") private boolean skipArkExecutable; /** * whether to keep ark biz jar after package finish, default value is true */ @Parameter(defaultValue = "true") private boolean keepArkBizJar; /** * web context path when biz is web app. it must start with "/", default value is "/" */ @Parameter(defaultValue = "/", required = true) private String webContextPath; /** * the biz jar will record the declared libraries if true, * and will filter out only declared libraries when delegate classes and resources to ark-base */ @Parameter(defaultValue = "false") private boolean declaredMode; @Parameter(defaultValue = "false") private boolean disableGitInfo; /*----------------Git 相关参数---------------------*/ /** * The root directory of the repository we want to check. */ @Parameter(defaultValue = "") private File gitDirectory; /** * 基座依赖标识,以 ${groupId}:${artifactId}:${version} 标识,或以 ${groupId}:${artifactId} 标识 */ @Parameter(defaultValue = "") private String baseDependencyParentIdentity; private File sofaArkLogDirectory; @Override public void execute() throws MojoExecutionException, MojoFailureException { if ("war".equals(this.mavenProject.getPackaging())) { getLog().debug("repackage goal could not be applied to war project."); return; } if ("pom".equals(this.mavenProject.getPackaging())) { getLog().debug("repackage goal could not be applied to pom project."); return; } if (StringUtils.equals(this.arkClassifier, this.bizClassifier)) { getLog().debug("Executable fat jar should be different from 'plug-in' module jar."); return; } if (this.skip) { getLog().debug("skipping repackaging as configuration."); return; } projectBuildingRequest = this.mavenProject.getProjectBuildingRequest(); sofaArkLogDirectory = new File(this.outputDirectory, "sofa-ark"); if (!sofaArkLogDirectory.exists()) { sofaArkLogDirectory.mkdirs(); } /* version of ark container packaged into fat jar follows the plugin version */ PluginDescriptor pluginDescriptor = (PluginDescriptor) getPluginContext().get( "pluginDescriptor"); arkVersion = pluginDescriptor.getVersion(); repackage(); } /** * * * @throws MojoExecutionException * @throws MojoFailureException */ private void repackage() throws MojoExecutionException, MojoFailureException { File source = this.mavenProject.getArtifact().getFile(); File appTarget = getAppTargetFile(); File moduleTarget = getModuleTargetFile(); Repackager repackager = getRepackager(source); Libraries libraries = new ArtifactsLibraries(getAdditionalArtifact(), this.requiresUnpack, getLog()); try { if (repackager.isDeclaredMode()) { Set artifactItems; if (MavenUtils.isRootProject(this.mavenProject)) { artifactItems = getAllArtifact(); } else { artifactItems = getAllArtifactByMavenTree(); } artifactItems.addAll(getDeclaredLibrariesWhitelist()); repackager.prepareDeclaredLibraries(artifactItems); } MavenProject rootProject = MavenUtils.getRootProject(this.mavenProject); repackager.setGitDirectory(getGitDirectory(rootProject)); repackager.repackage(appTarget, moduleTarget, libraries); } catch (IOException ex) { throw new MojoExecutionException(ex.getMessage(), ex); } updateArtifact(appTarget, repackager.getModuleTargetFile()); } protected Set getDeclaredLibrariesWhitelist() throws IOException { Set res = new HashSet<>(); Properties prop = ArkConfigHolder.getArkProperties(baseDir.getAbsolutePath()); Map arkYaml = ArkConfigHolder.getArkYaml(baseDir.getAbsolutePath()); Set declaredLibrariesWhitelist = new HashSet<>(); declaredLibrariesWhitelist.addAll(getStringSet(prop, DECLARED_LIBRARIES_WHITELIST)); declaredLibrariesWhitelist.addAll(getStringSet(arkYaml, DECLARED_LIBRARIES_WHITELIST)); for (String declaredLibrary : declaredLibrariesWhitelist) { res.add(parseArtifactItem(declaredLibrary)); } return res; } private File getGitDirectory(MavenProject rootProject) { if (disableGitInfo) { return null; } if (gitDirectory != null && gitDirectory.exists()) { return gitDirectory; } return com.alipay.sofa.ark.common.util.FileUtils.file(rootProject.getBasedir() .getAbsolutePath() + "/.git"); } private void parseArtifactItems(DependencyNode rootNode, Set result) { if (rootNode != null) { if (!StringUtils.equalsIgnoreCase(rootNode.getArtifact().getScope(), "test")) { result.add(parseArtifactItem(rootNode.getArtifact())); } if (CollectionUtils.isNotEmpty(rootNode.getChildren())) { for (DependencyNode node : rootNode.getChildren()) { parseArtifactItems(node, result); } } } } private DependencyNode parseDependencyGraph() throws MojoExecutionException, MojoFailureException { if (null == super.getDependencyGraph()) { super.execute(); } return super.getDependencyGraph(); } private Set getAllArtifact() throws MojoExecutionException, MojoFailureException { DependencyNode dependencyNode = parseDependencyGraph(); Set results = new HashSet<>(); parseArtifactItems(dependencyNode, results); return results; } private Set getAllArtifactByMavenTree() throws MojoExecutionException, MojoFailureException { MavenProject rootProject = MavenUtils.getRootProject(this.mavenProject); getLog().info("root project path: " + rootProject.getBasedir().getAbsolutePath()); // run maven dependency:tree try { if (this.mavenProject.getBasedir() != null) { doGetAllArtifactByMavenTree(this.mavenProject); return getAllArtifact(); } } catch (MojoExecutionException e) { getLog().warn( "execute dependency:tree failed, try to execute dependency:tree in root project"); } return doGetAllArtifactByMavenTree(MavenUtils.getRootProject(this.mavenProject)); } private Set doGetAllArtifactByMavenTree(MavenProject project) throws MojoExecutionException { File baseDir = project.getBasedir(); getLog().info("project path: " + baseDir.getAbsolutePath()); // dependency:tree InvocationRequest request = new DefaultInvocationRequest(); request.setPomFile(com.alipay.sofa.ark.common.util.FileUtils.file(baseDir.getAbsolutePath() + "/pom.xml")); Properties userProperties = projectBuildingRequest.getUserProperties(); String outputPath = baseDir.getAbsolutePath() + "/deps.log." + System.currentTimeMillis(); List goals = Stream.of("dependency:tree", "-DappendOutput=true", "-DoutputFile=\"" + outputPath + "\"").collect(Collectors.toList()); if (userProperties != null) { userProperties.forEach((key, value) -> { if (key instanceof String && StringUtils.equals("outputFile", (String) key)) { return; } goals.add(String.format("-D%s=%s", key, "\"" + value + "\"")); }); } getLog().info( "execute 'mvn dependency:tree' with command 'mvn " + String.join(" ", goals) + "'"); request.setGoals(goals); request.setBatchMode(mavenSession.getSettings().getInteractiveMode()); request.setProfiles(mavenSession.getSettings().getActiveProfiles()); setSettingsLocation(request); Invoker invoker = new DefaultInvoker(); try { InvocationResult result = invoker.execute(request); if (result.getExitCode() != 0) { throw new MojoExecutionException("execute dependency:tree failed", result.getExecutionException()); } String depTreeStr = FileUtils.readFileToString(FileUtils.getFile(outputPath), Charset.defaultCharset()); return MavenUtils.convert(depTreeStr); } catch (MavenInvocationException | IOException e) { throw new MojoExecutionException("execute dependency:tree failed", e); } finally { File outputFile = com.alipay.sofa.ark.common.util.FileUtils.file(outputPath); if (outputFile.exists()) { outputFile.delete(); } } } private void setSettingsLocation(InvocationRequest request) { File userSettingsFile = mavenSession.getRequest().getUserSettingsFile(); if (userSettingsFile != null && userSettingsFile.exists()) { request.setUserSettingsFile(userSettingsFile); } File globalSettingsFile = mavenSession.getRequest().getGlobalSettingsFile(); if (globalSettingsFile != null && globalSettingsFile.exists()) { request.setGlobalSettingsFile(globalSettingsFile); } } /** * @return sofa-ark-all and all maven project's non-excluded artifacts * @throws MojoExecutionException */ @SuppressWarnings("unchecked") private Set getAdditionalArtifact() throws MojoExecutionException { Artifact arkArtifact = repositorySystem.createArtifact(ArkConstants.getGroupId(), ArkConstants.getArtifactId(), arkVersion, ArkConstants.getScope(), ArkConstants.getType()); try { ArtifactResolutionRequest artifactResolutionRequest = new ArtifactResolutionRequest(); artifactResolutionRequest.setArtifact(arkArtifact); artifactResolutionRequest.setLocalRepository(projectBuildingRequest .getLocalRepository()); artifactResolutionRequest.setRemoteRepositories(this.mavenProject .getRemoteArtifactRepositories()); repositorySystem.resolve(artifactResolutionRequest); Set artifacts = new HashSet<>(Collections.singleton(arkArtifact)); // 读取需要打包的依赖 artifacts.addAll(getSlimmedArtifacts()); return artifacts; } catch (Exception ex) { throw new MojoExecutionException(ex.getMessage(), ex); } } private Set getSlimmedArtifacts() throws MojoExecutionException, IOException, MojoFailureException { if (StringUtils.endsWithAny(packExcludesConfig, ".yml", ".properties")) { throw new MojoExecutionException( "not support packExcludesConfig of .yml or .properties in sofa-ark-maven-plugin, please using default conf/ark/bootstrap.yml or conf/ark/bootstrap.properties"); } ModuleSlimConfig moduleSlimConfig = (new ModuleSlimConfig()) .setPackExcludesConfig(packExcludesConfig).setPackExcludesUrl(packExcludesUrl) .setExcludes(excludes).setExcludeGroupIds(excludeGroupIds) .setExcludeArtifactIds(excludeArtifactIds).setIncludes(includes) .setIncludeGroupIds(includeGroupIds).setIncludeArtifactIds(includeArtifactIds) .setBaseDependencyParentIdentity(baseDependencyParentIdentity); ModuleSlimExecutor slimStrategyExecutor = new ModuleSlimExecutor(this.mavenProject, this.repositorySystem, projectBuilder, parseDependencyGraph(), moduleSlimConfig, this.baseDir, this.sofaArkLogDirectory, this.getLog()); return slimStrategyExecutor.getSlimmedArtifacts(); } private File getAppTargetFile() { String classifier = (this.arkClassifier == null ? "" : this.arkClassifier.trim()); if (classifier.length() > 0 && !classifier.startsWith("-")) { classifier = "-" + classifier; } if (!this.outputDirectory.exists()) { this.outputDirectory.mkdirs(); } return new File(this.outputDirectory, this.finalName + classifier + "." + this.mavenProject.getArtifact() .getArtifactHandler().getExtension()); } private File getModuleTargetFile() { String classifier = (this.bizClassifier == null ? "" : this.bizClassifier.trim()); if (classifier.length() > 0 && !classifier.startsWith("-")) { classifier = "-" + classifier; } if (!this.outputDirectory.exists()) { this.outputDirectory.mkdirs(); } return new File(this.outputDirectory, this.finalName + classifier + "." + this.mavenProject.getArtifact() .getArtifactHandler().getExtension()); } private Repackager getRepackager(File source) { Repackager repackager = new Repackager(source); repackager.addMainClassTimeoutWarningListener(new LoggingMainClassTimeoutWarningListener()); repackager.setMainClass(this.mainClass); repackager.setBizName(bizName); if (!StringUtils.isEmpty(System.getProperty(BIZ_NAME))) { repackager.setBizName(System.getProperty(BIZ_NAME)); } repackager.setBizVersion(bizVersion); repackager.setPriority(String.valueOf(priority)); repackager.setArkVersion(arkVersion); repackager.setDenyImportClasses(denyImportClasses); repackager.setDenyImportPackages(denyImportPackages); repackager.setDenyImportResources(denyImportResources); repackager.setInjectPluginDependencies(injectPluginDependencies); repackager.setInjectPluginExportPackages(injectPluginExportPackages); repackager.setPackageProvided(packageProvided); repackager.setSkipArkExecutable(skipArkExecutable); repackager.setKeepArkBizJar(keepArkBizJar); repackager.setBaseDir(baseDir); repackager.setWebContextPath(webContextPath); repackager.setDeclaredMode(declaredMode); return repackager; } private void updateArtifact(File repackaged, File modulePackaged) { if (this.attach) { if (!this.skipArkExecutable) { attachArtifact(repackaged, arkClassifier); } if (this.keepArkBizJar) { attachArtifact(modulePackaged, bizClassifier); } } } private void attachArtifact(File jarFile, String classifier) { getLog().info("Attaching archive:" + jarFile + ", with classifier: " + classifier); this.projectHelper.attachArtifact(this.mavenProject, this.mavenProject.getPackaging(), classifier, jarFile); } private class LoggingMainClassTimeoutWarningListener implements Repackager.MainClassTimeoutWarningListener { @Override public void handleTimeoutWarning(long duration, String mainMethod) { getLog() .warn( String .format( "Searching for the main-class is taking some time: %dms, consider using the mainClass configuration parameter", duration)); } } public void setExcludes(String str) { this.excludes = parseToSet(str); } public void setExcludeGroupIds(String str) { this.excludeGroupIds = parseToSet(str); } public void setExcludeArtifactIds(String str) { this.excludeArtifactIds = parseToSet(str); } public void setDenyImportPackages(String str) { this.denyImportPackages = parseToSet(str); } public void setDenyImportClasses(String str) { this.denyImportClasses = parseToSet(str); } public void setDenyImportResources(String str) { this.denyImportResources = parseToSet(str); } public void setInjectPluginDependencies(String str) { this.injectPluginDependencies = parseToSet(str); } public void setInjectPluginExportPackages(String str) { this.injectPluginExportPackages = parseToSet(str); } private LinkedHashSet parseToSet(String str) { LinkedHashSet set = new LinkedHashSet<>(); if (StringUtils.isBlank(str)) { return set; } Arrays.stream(str.split(",")) .map(String::trim) .filter(StringUtils::isNotBlank) .forEach(set::add); return set; } public static class ArkConstants { private static String groupId = "com.alipay.sofa"; private static String artifactId = "sofa-ark-all"; private static String classifier = ""; private static String scope = "compile"; private static String type = "jar"; public static String getGroupId() { return groupId; } public static String getArtifactId() { return artifactId; } public static String getClassifier() { return classifier; } public static String getScope() { return scope; } public static String getType() { return type; } } } ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/main/java/com/alipay/sofa/ark/boot/mojo/model/ArkConfigHolder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo.model; import org.apache.maven.plugin.logging.SystemStreamLog; import org.yaml.snakeyaml.Yaml; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Map; import java.util.Properties; import static com.alipay.sofa.ark.spi.constant.Constants.ARK_CONF_BASE_DIR; import static com.alipay.sofa.ark.spi.constant.Constants.ARK_CONF_FILE; import static com.alipay.sofa.ark.spi.constant.Constants.ARK_CONF_YAML_FILE; import static com.google.common.collect.Maps.newHashMap; /** * @author lianglipeng.llp@alibaba-inc.com * @version $Id: ArkConfigHolder.java, v 0.1 2024年06月26日 13:45 立蓬 Exp $ */ public class ArkConfigHolder { private static Properties arkProperties; private static Map arkYaml; private static SystemStreamLog log = new SystemStreamLog(); public static Properties getArkProperties(String baseDir) throws IOException { return arkProperties == null ? initArkProperties(baseDir) : arkProperties; } public static Map getArkYaml(String baseDir) throws IOException { return arkYaml == null ? initArkYaml(baseDir) : arkYaml; } private static Map initArkYaml(String baseDir) throws IOException { String configPath = baseDir + File.separator + ARK_CONF_BASE_DIR + File.separator + ARK_CONF_YAML_FILE; File configFile = new File(configPath); if (!configFile.exists()) { log.info(String.format( "sofa-ark-maven-plugin: extension-config %s not found, will not config it", configPath)); return newHashMap(); } log.info(String.format( "sofa-ark-maven-plugin: find extension-config %s and will config it", configPath)); try (FileInputStream fis = new FileInputStream(configPath)) { Yaml yaml = new Yaml(); arkYaml = yaml.load(fis); return arkYaml; } catch (IOException ex) { log.error(String.format("failed to parse excludes artifacts from %s.", configPath), ex); throw ex; } } private static Properties initArkProperties(String baseDir) throws IOException { String configPath = baseDir + File.separator + ARK_CONF_BASE_DIR + File.separator + ARK_CONF_FILE; File configFile = new File(configPath); if (!configFile.exists()) { log.info(String.format( "sofa-ark-maven-plugin: extension-config %s not found, will not config it", configPath)); return new Properties(); } log.info(String.format( "sofa-ark-maven-plugin: find extension-config %s and will config it", configPath)); Properties prop = new Properties(); try (FileInputStream fis = new FileInputStream(configPath)) { prop.load(fis); arkProperties = prop; return prop; } catch (IOException ex) { log.error(String.format( "sofa-ark-maven-plugin: failed to read extension-config from %s.", configPath), ex); throw ex; } } } ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/main/java/com/alipay/sofa/ark/boot/mojo/utils/ParseUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo.utils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import static com.alipay.sofa.ark.spi.constant.Constants.COMMA_SPLIT; import static com.google.common.collect.Sets.newHashSet; /** * @author lianglipeng.llp@alibaba-inc.com * @version $Id: ParseUtils.java, v 0.1 2024年07月14日 01:24 立蓬 Exp $ */ public class ParseUtils { public static Set getStringSet(Properties prop, String confKey) { if (null == prop) { return newHashSet(); } String[] values = StringUtils.split(prop.getProperty(confKey), COMMA_SPLIT); if (values == null) { return newHashSet(); } values = Arrays.stream(values).map(String::trim).filter(s -> !s.isEmpty()).toArray(String[]::new); return newHashSet(values); } public static Set getStringSet(Map yaml, String confKey) { Object value = getValue(yaml, confKey); if (null == value) { return newHashSet(); } return newHashSet((List) value); } public static boolean getBooleanWithDefault(Map yaml, String confKey, boolean defaultValue) { Object value = getValue(yaml, confKey); if (null == value) { return defaultValue; } return Boolean.parseBoolean(value.toString()); } public static boolean getBooleanWithDefault(Properties prop, String confKey, boolean defaultValue) { Object value = prop.getProperty(confKey); if (null == value) { return defaultValue; } return Boolean.parseBoolean(value.toString()); } public static boolean getBooleanWithDefault(Properties prop, Map yaml, String confKey, boolean defaultValue) { Object valueFromProp = prop.getProperty(confKey); Object valueFromYaml = getValue(yaml, confKey); if (null == valueFromProp && null == valueFromYaml) { return defaultValue; } return null != valueFromProp ? Boolean.parseBoolean(valueFromProp.toString()) : Boolean .parseBoolean(valueFromYaml.toString()); } private static Object getValue(Map yaml, String confKey) { if (MapUtils.isEmpty(yaml) || StringUtils.isEmpty(confKey)) { return null; } List keys = Arrays.asList(StringUtils.split(confKey, ".")); String currentKey = keys.get(0); if (yaml.containsKey(currentKey) && null != yaml.get(currentKey) && keys.size() == 1) { return yaml.get(currentKey); } if (yaml.containsKey(currentKey) && null != yaml.get(currentKey) && keys.size() > 1 && yaml.get(currentKey) instanceof Map) { return getValue((Map) yaml.get(currentKey), StringUtils.join(keys.subList(1, keys.size()), ".")); } return null; } } ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/test/java/com/alipay/sofa/ark/boot/mojo/ArtifactsLibrariesTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DefaultArtifact; import org.apache.maven.artifact.handler.DefaultArtifactHandler; import org.apache.maven.model.Dependency; import org.apache.maven.monitor.logging.DefaultLog; import org.codehaus.plexus.logging.console.ConsoleLogger; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; public class ArtifactsLibrariesTest { public ArtifactsLibrariesTest() { } @Before public void setUp() { } @After public void tearDown() { } @Test public void testDoWithLibraries() throws IOException { Set artifacts = new HashSet<>(); DefaultArtifact artifact = new DefaultArtifact("group1", "artifact1", "1.0", "compile", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group1", "artifact1", "2.0", "provided", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group1", "artifact2", "2.0", "runtime", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group2", "artifact2", "2.0", "compile", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group2", "artifact3", "2.0", "provided", "", "clsf", new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group3", "artifact3", "2.0", "runtime", "", "clsf", new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group3", "artifact4", "2.0", null, "", "clsf", new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); List dependencies = new ArrayList<>(); Dependency dependency = new Dependency(); dependency.setGroupId("group3"); dependency.setArtifactId("artifact3"); dependencies.add(dependency); AtomicInteger atomicInteger = new AtomicInteger(0); new ArtifactsLibraries(artifacts, dependencies, new DefaultLog(new ConsoleLogger())).doWithLibraries( library -> atomicInteger.incrementAndGet()); assertEquals(artifacts.size() - 1, atomicInteger.get()); } } ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/test/java/com/alipay/sofa/ark/boot/mojo/CommonUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import java.io.File; import java.net.URISyntaxException; import java.net.URL; /** * @author lianglipeng.llp@alibaba-inc.com * @version $Id: CommonUtils.java, v 0.1 2024年08月22日 21:56 立蓬 Exp $ */ public class CommonUtils { public static File getResourceFile(String resourceName) throws URISyntaxException { URL url = CommonUtils.class.getClassLoader().getResource(resourceName); return new File(url.toURI()); } } ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/test/java/com/alipay/sofa/ark/boot/mojo/MavenUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import com.alipay.sofa.ark.tools.ArtifactItem; import org.apache.maven.project.MavenProject; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; import java.util.HashSet; import java.util.Set; import static com.alipay.sofa.ark.boot.mojo.MavenUtils.*; import static org.junit.Assert.assertEquals; public class MavenUtilsTest { @Before public void setUp() { } @After public void tearDown() { } @Test public void testIsRootProject() { assertEquals(true, isRootProject(null)); MavenProject parentMavenProject = new MavenProject(); parentMavenProject.setFile(new File("./a")); MavenProject mavenProject = new MavenProject(); mavenProject.setParent(parentMavenProject); assertEquals(false, isRootProject(mavenProject)); parentMavenProject.setFile(null); assertEquals(true, isRootProject(mavenProject)); assertEquals(null, getRootProject(null)); } @Test public void testConvert() { assertEquals(new HashSet<>(), convert("")); assertEquals(new HashSet<>(), convert("\n")); assertEquals(new HashSet<>(), convert("\n\r")); assertEquals(new HashSet<>(), convert("\n\r")); assertEquals(new HashSet<>(), convert("a\na")); Set artifactItems = new HashSet<>(); ArtifactItem artifactItem = new ArtifactItem(); artifactItem.setGroupId("org.springframework.boot"); artifactItem.setArtifactId("spring-boot"); artifactItem.setType("jar"); artifactItem.setVersion("2.7.14"); artifactItem.setScope("provided"); artifactItems.add(artifactItem); artifactItem = new ArtifactItem(); artifactItem.setGroupId("org.springframework"); artifactItem.setArtifactId("spring-jcl"); artifactItem.setType("jar"); artifactItem.setVersion("5.3.29"); artifactItem.setScope("provided"); artifactItem.setClassifier("ark-biz"); artifactItems.add(artifactItem); assertEquals( artifactItems, convert("[INFO] com.alipay.sofa:sofa-ark-springboot-starter:jar:2.2.4-SNAPSHOT\n" + "[INFO] +- org.springframework.boot:spring-boot:jar:2.7.14:provided\n" + "[INFO] | | \\- org.springframework:spring-jcl:jar:ark-biz:5.3.29:provided\n" + "[INFO] | \\- org.springframework:spring-context:jar:5.3.29")); } } ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/test/java/com/alipay/sofa/ark/boot/mojo/ModuleSlimExecutorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DefaultArtifact; import org.apache.maven.artifact.handler.DefaultArtifactHandler; import org.apache.maven.model.Dependency; import org.apache.maven.model.DependencyManagement; import org.apache.maven.model.Model; import org.apache.maven.model.Parent; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.apache.maven.shared.dependency.graph.DependencyNode; import org.junit.Test; import org.mockito.Mockito; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import static com.alipay.sofa.ark.boot.mojo.ReflectionUtils.setField; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * @author lianglipeng.llp@alibaba-inc.com * @version $Id: ModuleSlimStrategyTest.java, v 0.1 2024年07月24日 16:33 立蓬 Exp $ */ public class ModuleSlimExecutorTest { @Test public void testGetSlimmedArtifacts() throws MojoExecutionException, IOException, URISyntaxException { MavenProject proj = mock(MavenProject.class); Artifact a1 = new DefaultArtifact("com.alipay.sofa", "a1", "version", "compile", "jar", null, new DefaultArtifactHandler()); Artifact a2 = new DefaultArtifact("com.alipay.sofa", "a2", "version", "compile", "jar", null, new DefaultArtifactHandler()); Artifact a3 = new DefaultArtifact("com.alipay.sofa", "a3", "version", "compile", "jar", null, new DefaultArtifactHandler()); Artifact a4 = new DefaultArtifact("com.alipay.sofa", "a4", "version", "provided", "jar", null, new DefaultArtifactHandler()); Artifact a5 = new DefaultArtifact("com.alipay.sofa", "a5", "version", "compile", "jar", null, new DefaultArtifactHandler()); Set artifacts = Sets.newHashSet(a1, a2, a3, a4, a5); when(proj.getArtifacts()).thenReturn(artifacts); ModuleSlimConfig config = new ModuleSlimConfig(); ModuleSlimExecutor strategy = spy(new ModuleSlimExecutor(proj, null, null, null, config, mockBaseDir(), null, mockLog())); doNothing().when(strategy).checkExcludeByParentIdentity(anySet()); doReturn(Sets.newHashSet(a1)).when(strategy).getArtifactsToFilterByParentIdentity(anySet()); doReturn(Sets.newHashSet(a2)).when(strategy).getArtifactsToFilterByExcludeConfig(anySet()); doReturn(Sets.newHashSet(a3, a4)).when(strategy).getArtifactsToFilterByBasePlugin(anySet()); doReturn(Sets.newHashSet(a4)).when(strategy).getArtifactsToAddByIncludeConfig(anySet()); assertEquals(2, strategy.getSlimmedArtifacts().size()); assertEquals(Artifact.SCOPE_COMPILE, a4.getScope()); } @Test public void testGetArtifactsToFilterByParentIdentity() throws URISyntaxException, MojoExecutionException { ModuleSlimConfig config = (new ModuleSlimConfig()) .setBaseDependencyParentIdentity("com.mock:base-dependencies-starter:1.0"); ModuleSlimExecutor strategy = new ModuleSlimExecutor(getMockBootstrapProject(), null, null, null, config, mockBaseDir(), null, mockLog()); Artifact sameArtifact = mock(Artifact.class); when(sameArtifact.getGroupId()).thenReturn("com.mock"); when(sameArtifact.getArtifactId()).thenReturn("same-dependency-artifact"); when(sameArtifact.getVersion()).thenReturn("1.0"); when(sameArtifact.getBaseVersion()).thenReturn("1.0-SNAPSHOT"); when(sameArtifact.getType()).thenReturn("jar"); Artifact differenceArtifact = mock(Artifact.class); when(differenceArtifact.getGroupId()).thenReturn("com.mock"); when(differenceArtifact.getArtifactId()).thenReturn("difference-dependency-artifact"); when(differenceArtifact.getVersion()).thenReturn("2.0"); when(differenceArtifact.getBaseVersion()).thenReturn("2.0-SNAPSHOT"); when(sameArtifact.getType()).thenReturn("jar"); // case1: with BaseDependencyParentIdentity Set res = strategy.getArtifactsToFilterByParentIdentity(Sets.newHashSet( sameArtifact, differenceArtifact)); assertTrue(res.contains(sameArtifact)); assertFalse(res.contains(differenceArtifact)); // case2: without BaseDependencyParentIdentity config.setBaseDependencyParentIdentity(""); res = strategy.getArtifactsToFilterByParentIdentity(Sets.newHashSet(sameArtifact, differenceArtifact)); assertTrue(res.isEmpty()); } @Test public void testGetArtifactsToFilterByBasePlugin() throws URISyntaxException { ModuleSlimConfig config = new ModuleSlimConfig(); ModuleSlimExecutor strategy = spy(new ModuleSlimExecutor(getMockBootstrapProject(), null, null, null, config, mockBaseDir(), null, mockLog())); Artifact sameArtifact = mock(Artifact.class); when(sameArtifact.getGroupId()).thenReturn("com.mock"); when(sameArtifact.getArtifactId()).thenReturn("same-dependency-artifact"); when(sameArtifact.getVersion()).thenReturn("1.0"); when(sameArtifact.getBaseVersion()).thenReturn("1.0-SNAPSHOT"); when(sameArtifact.getType()).thenReturn("jar"); Artifact differenceArtifact = mock(Artifact.class); when(differenceArtifact.getGroupId()).thenReturn("com.mock"); when(differenceArtifact.getArtifactId()).thenReturn("difference-dependency-artifact"); when(differenceArtifact.getVersion()).thenReturn("2.0"); when(differenceArtifact.getBaseVersion()).thenReturn("2.0-SNAPSHOT"); when(sameArtifact.getType()).thenReturn("jar"); doReturn(mockBasePluginBomModel(Lists.newArrayList(sameArtifact))).when(strategy) .resolvePomAsOriginalModel(anyString(), anyString(), anyString()); // case1: without BaseDependencyParentIdentity config.setBaseDependencyParentIdentity(""); Set res = strategy.getArtifactsToFilterByBasePlugin(Sets.newHashSet(sameArtifact, differenceArtifact)); assertTrue(res.isEmpty()); // case2: with BaseDependencyParentIdentity config.setBaseDependencyParentIdentity("com.mock:base-dependencies-starter:1.0"); res = strategy.getArtifactsToFilterByBasePlugin(Sets.newHashSet(sameArtifact, differenceArtifact)); assertTrue(res.contains(sameArtifact)); assertFalse(res.contains(differenceArtifact)); } private Model mockBasePluginBomModel(List artifacts){ Model model = new Model(); model.setGroupId("com.mock"); model.setArtifactId("base-plugin-bom"); model.setVersion("1.0"); model.setPackaging("pom"); Parent parent = new Parent(); parent.setGroupId("com.mock"); parent.setArtifactId("base-dependencies-starter"); parent.setVersion("1.0"); model.setParent(parent); DependencyManagement dependencyManagement = new DependencyManagement(); dependencyManagement.setDependencies(artifacts.stream().map(artifact -> { Dependency d = new Dependency(); d.setGroupId(artifact.getGroupId()); d.setArtifactId(artifact.getArtifactId()); d.setVersion(artifact.getBaseVersion()); return d; }).collect(Collectors.toList())); model.setDependencyManagement(dependencyManagement); return model; } @Test public void testCheckExcludeByParentIdentity() throws URISyntaxException, MojoExecutionException { ModuleSlimConfig config = (new ModuleSlimConfig()) .setBaseDependencyParentIdentity("com.mock:base-dependencies-starter:1.0"); Log log = Mockito.mock(Log.class); doNothing().when(log).info(anyString()); doNothing().when(log).error(anyString()); ModuleSlimExecutor strategy = new ModuleSlimExecutor(getMockBootstrapProject(), null, null, null, config, mockBaseDir(), null, log); // 基座和模块都有该依赖,且版本一致 Artifact sameArtifact = mock(Artifact.class); when(sameArtifact.getGroupId()).thenReturn("com.mock"); when(sameArtifact.getArtifactId()).thenReturn("same-dependency-artifact"); when(sameArtifact.getVersion()).thenReturn("1.0"); when(sameArtifact.getBaseVersion()).thenReturn("1.0-SNAPSHOT"); when(sameArtifact.getType()).thenReturn("jar"); // 模块有,但基座没有的依赖 Artifact differentArtifact = mock(Artifact.class); when(differentArtifact.getGroupId()).thenReturn("com.mock"); when(differentArtifact.getArtifactId()).thenReturn("different-artifact"); when(differentArtifact.getVersion()).thenReturn("1.0"); when(differentArtifact.getBaseVersion()).thenReturn("1.0-SNAPSHOT"); when(differentArtifact.getType()).thenReturn("jar"); // 模块和基座都有该依赖,但版本不一致 Artifact differentVersionArtifact = mock(Artifact.class); when(differentVersionArtifact.getGroupId()).thenReturn("com.mock"); when(differentVersionArtifact.getArtifactId()).thenReturn("difference-dependency-artifact"); when(differentVersionArtifact.getVersion()).thenReturn("2.0"); when(differentVersionArtifact.getBaseVersion()).thenReturn("2.0-SNAPSHOT"); when(differentVersionArtifact.getType()).thenReturn("jar"); Dependency differenceVersionDependency = new Dependency(); differenceVersionDependency.setArtifactId("difference-dependency-artifact"); differenceVersionDependency.setGroupId("com.mock"); differenceVersionDependency.setVersion("1.0-SNAPSHOT"); // case1: 排除相同的依赖 Set toFilterByExclude = Sets.newHashSet(sameArtifact); strategy.checkExcludeByParentIdentity(toFilterByExclude); verify(log) .info( eq("check excludeWithBaseDependencyParentIdentity success with base: com.mock:base-dependencies-starter:1.0")); // case2: 排除了基座没有的依赖 toFilterByExclude = Sets.newHashSet(differentArtifact); try { strategy.checkExcludeByParentIdentity(toFilterByExclude); } catch (MojoExecutionException e) { // 验证构建失败 verify(log) .error( eq(String .format( "error to exclude package jar: %s because no such jar in base, please keep the jar or add it to base", MavenUtils.getArtifactIdentity(differentArtifact)))); assertEquals(String.format( "check excludeWithBaseDependencyParentIdentity failed with base: %s", config.getBaseDependencyParentIdentity()), e.getMessage()); } // case3: 排除了不同版本的依赖 toFilterByExclude = Sets.newHashSet(differentVersionArtifact); strategy.checkExcludeByParentIdentity(toFilterByExclude); verify(log) .error( eq(String .format( "error to exclude package jar: %s because it has different version with: %s in base, please keep the jar or set same version with base", MavenUtils.getArtifactIdentity(differentVersionArtifact), MavenUtils.getDependencyIdentity(differenceVersionDependency)))); // case4: 配置开关:如果排除的依赖有问题,那么构建报错 config.setBuildFailWhenExcludeBaseDependencyWithDiffVersion(true); try { strategy.checkExcludeByParentIdentity(toFilterByExclude); } catch (MojoExecutionException e) { // 验证构建失败 assertEquals(String.format( "check excludeWithBaseDependencyParentIdentity failed with base: %s", config.getBaseDependencyParentIdentity()), e.getMessage()); } } @Test public void testGetBaseDependencyParentOriginalModel() throws URISyntaxException { // find base-dependency-parent by gav identity ModuleSlimConfig config = (new ModuleSlimConfig()) .setBaseDependencyParentIdentity("com.mock:base-dependencies-starter:1.0"); ModuleSlimExecutor strategy = new ModuleSlimExecutor(getMockBootstrapProject(), null, null, null, config, mockBaseDir(), null, null); assertNotNull(strategy.getBaseDependencyParentOriginalModel()); // find base-dependency-parent by ga identity config.setBaseDependencyParentIdentity("com.mock:base-dependencies-starter"); assertNotNull(strategy.getBaseDependencyParentOriginalModel()); } @Test public void testExtensionExcludeAndIncludeArtifactsByDefault() throws URISyntaxException, IOException { ModuleSlimConfig config = new ModuleSlimConfig(); ModuleSlimExecutor strategy = new ModuleSlimExecutor(getMockBootstrapProject(), null, null, null, config, mockBaseDir(), null, mockLog()); strategy.configExcludeArtifactsByDefault(); // 验证 ark.properties assertTrue(config.getExcludes().contains("commons-beanutils:commons-beanutils")); assertTrue(config.getExcludeGroupIds().contains("org.springframework")); assertTrue(config.getExcludeArtifactIds().contains("sofa-ark-spi")); assertTrue(config.getIncludes().contains("com.alipay.sofa:sofa-ark-all")); assertTrue(config.getIncludeGroupIds().contains("com.alipay.sofa")); assertTrue(config.getIncludeArtifactIds().contains("sofa-ark-all")); // 验证 ark.yml assertTrue(config.getExcludes().contains("commons-beanutils:commons-beanutils-yml")); assertTrue(config.getExcludeGroupIds().contains("org.springframework-yml")); assertTrue(config.getExcludeArtifactIds().contains("sofa-ark-spi-yml")); assertTrue(config.getIncludes().contains("com.alipay.sofa:sofa-ark-all-yml")); assertTrue(config.getIncludeGroupIds().contains("com.alipay.sofa")); assertTrue(config.getIncludeArtifactIds().contains("sofa-ark-all-yml")); } @Test public void testExtensionExcludeAndIncludeArtifacts() throws URISyntaxException { ModuleSlimConfig config = new ModuleSlimConfig(); ModuleSlimExecutor strategy = new ModuleSlimExecutor(null, null, null, null, config, mockBaseDir(), null, mockLog()); URL resource = this.getClass().getClassLoader().getResource("excludes.txt"); strategy.extensionExcludeAndIncludeArtifacts(resource.getPath()); assertTrue(config.getExcludes().contains("tracer-core:3.0.10") && config.getExcludes().contains("tracer-core:3.0.11")); } @Test public void testLogExcludeMessage() throws URISyntaxException { List jarGroupIds = asList("com.alipay.sofa", "org.springframework"); List jarArtifactIds = asList("netty"); List jarList = asList("commons-io:commons-io:2.7"); DefaultArtifact defaultArtifact = new DefaultArtifact("com.alipay.sofa", "artifactId", "version", "compile", "jar", null, new DefaultArtifactHandler()); DefaultArtifact defaultArtifact1 = new DefaultArtifact("io.netty", "netty", "version", "compile", "jar", null, new DefaultArtifactHandler()); DefaultArtifact defaultArtifact2 = new DefaultArtifact("commons-io", "commons-io", "2.7", "compile", "jar", null, new DefaultArtifactHandler()); Set artifacts = new HashSet<>(); artifacts.add(defaultArtifact); artifacts.add(defaultArtifact1); artifacts.add(defaultArtifact2); ModuleSlimExecutor strategy = new ModuleSlimExecutor(null, null, null, null, null, mockBaseDir(), null, mockLog()); strategy.logExcludeMessage(jarGroupIds, jarArtifactIds, jarList, artifacts, true); strategy.logExcludeMessage(jarGroupIds, jarArtifactIds, jarList, artifacts, false); } @Test public void testExtensionExcludeAndIncludeArtifactsFromUrl() throws URISyntaxException, Exception { // 准备测试数据 DefaultArtifact defaultArtifact = new DefaultArtifact("groupId", "artifactId", "version", "provided", "jar", null, new DefaultArtifactHandler()); DefaultArtifact defaultArtifact1 = new DefaultArtifact("groupId", "artifactId", "version", "provided", "jar", null, new DefaultArtifactHandler()); Set artifacts = new HashSet<>(); artifacts.add(defaultArtifact); artifacts.add(defaultArtifact1); String packExcludesUrl = "http://mock-server.com/excludes"; // Mock HTTP响应 org.apache.http.client.methods.CloseableHttpResponse mockResponse = mock(org.apache.http.client.methods.CloseableHttpResponse.class); org.apache.http.StatusLine mockStatusLine = mock(org.apache.http.StatusLine.class); org.apache.http.HttpEntity mockEntity = mock(org.apache.http.HttpEntity.class); // 设置状态码为200 when(mockStatusLine.getStatusCode()).thenReturn(200); when(mockResponse.getStatusLine()).thenReturn(mockStatusLine); when(mockResponse.getEntity()).thenReturn(mockEntity); // 模拟返回的JSON数据 ModuleSlimExecutor.ExcludeConfig excludeConfig = new ModuleSlimExecutor.ExcludeConfig(); excludeConfig.setJarBlackGroupIds(Lists.newArrayList("com.test.black")); excludeConfig.setJarBlackArtifactIds(Lists.newArrayList("test-artifact-black")); excludeConfig.setJarBlackList(Lists.newArrayList("com.test.black:test-artifact-black:1.0")); excludeConfig.setJarWhiteGroupIds(Lists.newArrayList("com.test.white")); excludeConfig.setJarWhiteArtifactIds(Lists.newArrayList("test-artifact-white")); excludeConfig.setJarWhiteList(Lists.newArrayList("com.test.white:test-artifact-white:1.0")); excludeConfig.setJarWarnList(Lists.newArrayList("com.test.warn:test-artifact-warn:1.0")); excludeConfig.setJarWarnArtifactIds(Lists.newArrayList("test-artifact-warn")); excludeConfig.setJarWarnGroupIds(Lists.newArrayList("com.test.warn")); ModuleSlimExecutor.ExcludeConfigResponse excludeConfigResponse = new ModuleSlimExecutor.ExcludeConfigResponse(); excludeConfigResponse.setSuccess(true); excludeConfigResponse.setResult(excludeConfig); ObjectMapper objectMapper = new ObjectMapper(); String mockJsonResponse = objectMapper.writeValueAsString(excludeConfigResponse); when(mockEntity.getContent()).thenReturn(new java.io.ByteArrayInputStream(mockJsonResponse.getBytes())); // Mock HTTP客户端 org.apache.http.impl.client.CloseableHttpClient mockClient = mock(org.apache.http.impl.client.CloseableHttpClient.class); when(mockClient.execute(org.mockito.ArgumentMatchers.any(org.apache.http.client.methods.HttpGet.class))) .thenReturn(mockResponse); // 使用Mockito的静态方法mock功能 try (org.mockito.MockedStatic httpClientsMock = org.mockito.Mockito.mockStatic(org.apache.http.impl.client.HttpClients.class)) { httpClientsMock.when(org.apache.http.impl.client.HttpClients::createDefault) .thenReturn(mockClient); ModuleSlimConfig config = new ModuleSlimConfig(); ModuleSlimExecutor strategy = new ModuleSlimExecutor(null, null, null, null, config, mockBaseDir(), null, mockLog()); // 执行测试方法 strategy.extensionExcludeArtifactsFromUrl(packExcludesUrl, artifacts); // 验证配置是否正确设置 assertTrue(config.getExcludeGroupIds().contains("com.test.black")); assertTrue(config.getExcludeArtifactIds().contains("test-artifact-black")); assertTrue(config.getExcludes().contains("com.test.black:test-artifact-black:1.0")); assertTrue(config.getIncludeGroupIds().contains("com.test.white")); assertTrue(config.getIncludeArtifactIds().contains("test-artifact-white")); assertTrue(config.getIncludes().contains("com.test.white:test-artifact-white:1.0")); } } @Test public void testLogExcludeMessageWithMoreCases() throws URISyntaxException { List jarGroupIds = new ArrayList<>(); jarGroupIds.add("group1*"); jarGroupIds.add("group2.*"); List jarArtifactIds = new ArrayList<>(); jarArtifactIds.add("artifact1*"); jarArtifactIds.add("artifact2.g.*"); List jarList = new ArrayList<>(); Set artifacts = new HashSet<>(); Artifact artifact = new DefaultArtifact("group1.a.b", "artifact1gkl", "1.0", "test", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group2.c", "artifact1gkl", "1.0", "", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group3", "artifact1.e", "1.0", "", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group3", "artifact2.g.h", "1.0", "", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); ModuleSlimExecutor strategy = new ModuleSlimExecutor(null, null, null, null, null, mockBaseDir(), null, mockLog()); strategy.logExcludeMessage(jarGroupIds, jarArtifactIds, jarList, artifacts, true); strategy.logExcludeMessage(jarGroupIds, jarArtifactIds, jarList, artifacts, false); } @Test public void testExcludeWithoutItsDependencies() throws URISyntaxException { MavenProject proj = mock(MavenProject.class); Artifact a1 = mockArtifact("com.exclude", "a1", "1.0.0", "jar", null, "compile"); Artifact a2 = mockArtifact("com.exclude.group.id", "a2", "1.0.0", "jar", null, "compile"); Artifact a3 = mockArtifact("com.exclude.artifact.id", "a3", "1.0.0", "jar", null, "compile"); Artifact a4 = mockArtifact("com.include", "a4", "1.0.0", "jar", null, "compile"); Set artifacts = Sets.newHashSet(a1, a2, a3, a4); ModuleSlimConfig moduleSlimConfig = new ModuleSlimConfig(); moduleSlimConfig.setExcludes(Sets.newLinkedHashSet(Collections .singletonList("com.exclude:a1"))); moduleSlimConfig.setExcludeGroupIds(Sets.newLinkedHashSet(Collections .singletonList("com.exclude.group.id"))); moduleSlimConfig.setExcludeArtifactIds(Sets.newLinkedHashSet(Collections .singletonList("a3"))); moduleSlimConfig.setExcludeWithIndirectDependencies(false); ModuleSlimExecutor strategy = spy(new ModuleSlimExecutor(proj, null, null, null, moduleSlimConfig, mockBaseDir(), null, mockLog())); Set res = strategy.getArtifactsToFilterByExcludeConfig(artifacts); assertEquals(3, res.size()); } @Test public void testExcludeWithIndirectDependencies() throws URISyntaxException { MavenProject proj = mock(MavenProject.class); Artifact a1 = mockArtifact("com.exclude", "a1", "1.0.0", "jar", null, "compile"); Artifact a2 = mockArtifact("com.exclude.group.id", "a2", "1.0.0", "jar", null, "compile"); Artifact a3 = mockArtifact("com.exclude.artifact.id", "a3", "1.0.0", "jar", null, "compile"); Artifact a4 = mockArtifact("com.include", "a4", "1.0.0", "jar", null, "compile"); Artifact d1 = mockArtifact("com.exclude.dependency", "d1", "1.0.0", "jar", null, "compile"); Artifact d2 = mockArtifact("com.exclude.dependency", "d2", "1.0.0", "jar", null, "compile"); Artifact d3 = mockArtifact("com.exclude.dependency", "d3", "1.0.0", "jar", null, "compile"); Artifact d4 = mockArtifact("com.exclude.dependency", "d4", "1.0.0", "jar", null, "compile"); Artifact d5 = mockArtifact("com.include.dependency", "d5", "1.0.0", "jar", null, "compile"); Set artifacts = Sets.newHashSet(a1, a2, a3, a4, d1,d2,d3,d4,d5); ModuleSlimConfig moduleSlimConfig = new ModuleSlimConfig(); moduleSlimConfig.setExcludes(Sets.newLinkedHashSet(Collections.singletonList("com.exclude:a1"))); moduleSlimConfig.setExcludeGroupIds(Sets.newLinkedHashSet(Collections.singletonList("com.exclude.group.id"))); moduleSlimConfig.setExcludeArtifactIds(Sets.newLinkedHashSet(Collections.singletonList("a3"))); moduleSlimConfig.setExcludeWithIndirectDependencies(true); /* * 依赖关系如下: * -> a1 -> d1 -> d2 * / * root -> a2 -> a3 -> d3 * \ \ * \ -> d4 * -> a4 -> d5 * 在此依赖关系下,a1, a2, a3 会因为 exclude 被排除 * d1, d2, d3, d4 会因为 excludeWithDependencies 被排除 * a4, d5 不会被排除 */ DependencyNode root = mockNode(mockArtifact("com.mock", "root", "1.0", "jar", null, "compile")); DependencyNode a1Node = mockNode(a1); DependencyNode a2Node = mockNode(a2); DependencyNode a3Node = mockNode(a3); DependencyNode a4Node = mockNode(a4); DependencyNode d1Node = mockNode(d1); DependencyNode d2Node = mockNode(d2); DependencyNode d3Node = mockNode(d3); DependencyNode d4Node = mockNode(d4); DependencyNode d5Node = mockNode(d5); when(root.getChildren()).thenReturn(Lists.newArrayList(a1Node, a2Node,a4Node)); when(a1Node.getChildren()).thenReturn(Lists.newArrayList(d1Node)); when(d1Node.getChildren()).thenReturn(Lists.newArrayList(d2Node)); when(a2Node.getChildren()).thenReturn(Lists.newArrayList(a3Node)); when(a3Node.getChildren()).thenReturn(Lists.newArrayList(d3Node,d4Node)); when(a4Node.getChildren()).thenReturn(Lists.newArrayList(d5Node)); when(d2Node.getChildren()).thenReturn(Lists.newArrayList()); when(d3Node.getChildren()).thenReturn(Lists.newArrayList(d4Node)); when(d4Node.getChildren()).thenReturn(Lists.newArrayList()); ModuleSlimExecutor strategy = spy(new ModuleSlimExecutor(proj,null,null, root, moduleSlimConfig, mockBaseDir(), null, mockLog())); Set res = strategy.getArtifactsToFilterByExcludeConfig(artifacts); assertEquals(7, res.size()); Set resIdentities = res.stream().map(Artifact::getArtifactId).collect(Collectors.toSet()); assertTrue(resIdentities.contains("a1")); assertTrue(resIdentities.contains("a2")); assertTrue(resIdentities.contains("a3")); assertTrue(resIdentities.contains("d1")); assertTrue(resIdentities.contains("d2")); assertTrue(resIdentities.contains("d3")); assertTrue(resIdentities.contains("d4")); } private DependencyNode mockNode(Artifact artifact) { DependencyNode node = mock(DependencyNode.class); when(node.getArtifact()).thenReturn(artifact); return node; } private Artifact mockArtifact(String groupId, String artifactId, String version, String type, String classifier, String scope) { Artifact artifact = mock(Artifact.class); when(artifact.getArtifactId()).thenReturn(artifactId); when(artifact.getGroupId()).thenReturn(groupId); when(artifact.getVersion()).thenReturn(version); when(artifact.getType()).thenReturn(type); when(artifact.getClassifier()).thenReturn(classifier); when(artifact.getScope()).thenReturn(scope); return artifact; } private MavenProject getMockBootstrapProject() throws URISyntaxException { MavenProject project = new MavenProject(); project.setArtifactId("base-bootstrap"); project.setGroupId("com.mock"); project.setVersion("0.0.1-SNAPSHOT"); project.setPackaging("jar"); Artifact artifact = mock(Artifact.class); when(artifact.getArtifactId()).thenReturn("base-bootstrap"); when(artifact.getGroupId()).thenReturn("com.mock"); when(artifact.getVersion()).thenReturn("0.0.1-SNAPSHOT"); project.setArtifact(artifact); project.setParent(getRootProject()); Model model = new Model(); model.setGroupId("com.mock"); model.setArtifactId("base-bootstrap"); model.setVersion("0.0.1-SNAPSHOT"); DependencyManagement dependencyManagement = new DependencyManagement(); Dependency d = new Dependency(); d.setType("pom"); d.setScope("import"); d.setGroupId("com.mock"); d.setArtifactId("base-plugin-bom"); d.setVersion("1.0"); dependencyManagement.setDependencies(Lists.newArrayList(d)); model.setDependencyManagement(dependencyManagement); project.setOriginalModel(model); setField("basedir", project, CommonUtils.getResourceFile("baseDir")); return project; } private MavenProject getRootProject() { MavenProject project = new MavenProject(); project.setArtifactId("base-dependencies-starter"); project.setGroupId("com.mock"); project.setVersion("1.0"); project.setPackaging("pom"); Artifact artifact = mock(Artifact.class); when(artifact.getArtifactId()).thenReturn("base-dependencies-starter"); when(artifact.getGroupId()).thenReturn("com.mock"); when(artifact.getVersion()).thenReturn("1.0"); when(artifact.getBaseVersion()).thenReturn("1.0"); project.setArtifact(artifact); project.setParent(null); Dependency sameDependency = new Dependency(); sameDependency.setArtifactId("same-dependency-artifact"); sameDependency.setGroupId("com.mock"); sameDependency.setVersion("1.0-SNAPSHOT"); Dependency differenceDependency = new Dependency(); differenceDependency.setArtifactId("difference-dependency-artifact"); differenceDependency.setGroupId("com.mock"); differenceDependency.setVersion("1.0-SNAPSHOT"); DependencyManagement dm = new DependencyManagement(); dm.setDependencies(Lists.newArrayList(sameDependency, differenceDependency)); Model pom = new Model(); pom.setDependencyManagement(dm); project.setOriginalModel(pom); return project; } private Log mockLog() { Log log = mock(Log.class); doNothing().when(log).info(anyString()); return log; } private File mockBaseDir() throws URISyntaxException { return CommonUtils.getResourceFile("baseDir"); } } ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/test/java/com/alipay/sofa/ark/boot/mojo/ReflectionUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import java.lang.reflect.Field; /** * @author lianglipeng.llp@alibaba-inc.com * @version $Id: ReflectionUtils.java, v 0.1 2024年07月24日 19:37 立蓬 Exp $ */ public class ReflectionUtils { public static void setField(String fieldName, Object o, T value) { Class klass = o.getClass(); while (klass != null) { try { Field f = klass.getDeclaredField(fieldName); f.setAccessible(true); f.set(o, value); return; } catch (Exception e) { klass = klass.getSuperclass(); } } } } ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/test/java/com/alipay/sofa/ark/boot/mojo/RepackageMojoTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import com.alipay.sofa.ark.boot.mojo.ModuleSlimExecutor.ExcludeConfig; import com.alipay.sofa.ark.boot.mojo.ModuleSlimExecutor.ExcludeConfigResponse; import com.alipay.sofa.ark.tools.ArtifactItem; import com.google.common.collect.Lists; import com.google.common.io.Files; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.FileUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DefaultArtifact; import org.apache.maven.artifact.handler.DefaultArtifactHandler; import org.apache.maven.execution.DefaultMavenExecutionRequest; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.project.DefaultProjectBuildingRequest; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectHelper; import org.apache.maven.repository.RepositorySystem; import org.apache.maven.settings.Settings; import org.apache.maven.shared.dependency.graph.DependencyNode; import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyNode; import org.apache.maven.shared.invoker.DefaultInvocationRequest; import org.apache.maven.shared.invoker.InvocationRequest; import org.junit.After; import org.junit.Assert; import org.junit.Test; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import static com.alipay.sofa.ark.boot.mojo.RepackageMojo.ArkConstants.getClassifier; import static java.lang.System.clearProperty; import static java.lang.System.setProperty; import static org.apache.commons.beanutils.BeanUtils.copyProperties; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author guolei.sgl (guolei.sgl@antfin.com) 2020/12/16 2:25 下午 * @since **/ public class RepackageMojoTest { @After public void tearDown() { clearProperty("maven.home"); } /** * 测试依赖解析 * * @throws NoSuchMethodException * @throws InvocationTargetException * @throws IllegalAccessException */ @Test public void testParseArtifactItems() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { /** * 构建依赖图 * * biz-child1-child1 * / * biz-child1 * / \ * / biz-child1-child2 * biz * \ biz-child2-child1 * \ / * biz-child2 * \ * biz-child2-child1 * */ DefaultDependencyNode bizNode = buildDependencyNode(null, "com.alipay.sofa", "biz", "1.0.0"); DefaultDependencyNode bizChild1 = buildDependencyNode(bizNode, "com.alipay.sofa", "biz-child1", "1.0.0"); DefaultDependencyNode bizChild2 = buildDependencyNode(bizNode, "com.alipay.sofa", "biz-child2", "1.0.0"); buildDependencyNode(bizChild1, "com.alipay.sofa", "biz-child1-child1", "1.0.0"); buildDependencyNode(bizChild1, "com.alipay.sofa", "biz-child1-child2", "1.0.0"); buildDependencyNode(bizChild2, "com.alipay.sofa", "biz-child2-child1", "1.0.0"); buildDependencyNode(bizChild2, "com.alipay.sofa", "biz-child2-child2", "1.0.0"); RepackageMojo repackageMojo = new RepackageMojo(); Method parseArtifactItems = repackageMojo.getClass().getDeclaredMethod( "parseArtifactItems", DependencyNode.class, Set.class); parseArtifactItems.setAccessible(true); Set artifactItems = new HashSet<>(); parseArtifactItems.invoke(repackageMojo, bizNode, artifactItems); assertTrue(artifactItems.size() == 7); } private DefaultDependencyNode buildDependencyNode(DefaultDependencyNode parent, String groupId, String artifactId, String version) { DefaultDependencyNode dependencyNode = new DefaultDependencyNode(parent, new DefaultArtifact(groupId, artifactId, version, "provided", "jar", "biz-jar", null), null, null, null); if (parent != null) { if (CollectionUtils.isEmpty(parent.getChildren())) { parent.setChildren(Lists.newArrayList()); } parent.getChildren().add(dependencyNode); } return dependencyNode; } @Test public void testSetSettingsLocation() throws Exception { String userSettingsFilePath = System.getProperty("user.home") + File.separator + "user-settings-test.xml"; String globalSettingsFilePath = System.getProperty("user.home") + File.separator + "global-settings-test.xml"; File userSettingsFile = com.alipay.sofa.ark.common.util.FileUtils .file(userSettingsFilePath); File globalSettingsFile = com.alipay.sofa.ark.common.util.FileUtils .file(globalSettingsFilePath); InvocationRequest request = new DefaultInvocationRequest(); invokeSetSettingsLocation(request, userSettingsFilePath, globalSettingsFilePath); Assert.assertNull(request.getUserSettingsFile()); Assert.assertNull(request.getGlobalSettingsFile()); Files.touch(globalSettingsFile); invokeSetSettingsLocation(request, userSettingsFilePath, globalSettingsFilePath); Assert.assertNull(request.getUserSettingsFile()); assertNotNull(request.getGlobalSettingsFile()); Files.touch(userSettingsFile); invokeSetSettingsLocation(request, userSettingsFilePath, globalSettingsFilePath); assertNotNull(request.getUserSettingsFile()); assertNotNull(request.getGlobalSettingsFile()); FileUtils.deleteQuietly(userSettingsFile); FileUtils.deleteQuietly(globalSettingsFile); } private void invokeSetSettingsLocation(InvocationRequest request, String userSettingsFilePath, String globalSettingsFilePath) throws Exception { RepackageMojo repackageMojo = new RepackageMojo(); MavenExecutionRequest executionRequest = new DefaultMavenExecutionRequest(); executionRequest.setUserSettingsFile(com.alipay.sofa.ark.common.util.FileUtils .file(userSettingsFilePath)); executionRequest.setGlobalSettingsFile(com.alipay.sofa.ark.common.util.FileUtils .file(globalSettingsFilePath)); // 构造对象 MavenSession mavenSession = new MavenSession(null, executionRequest, null, new ArrayList<>()); Field mavenSessionField = repackageMojo.getClass().getDeclaredField("mavenSession"); mavenSessionField.setAccessible(true); mavenSessionField.set(repackageMojo, mavenSession); Method setSettingsLocation = repackageMojo.getClass().getDeclaredMethod( "setSettingsLocation", InvocationRequest.class); setSettingsLocation.setAccessible(true); setSettingsLocation.invoke(repackageMojo, request); } @Test public void testIsSameWithVersion() { ArtifactItem artifactItem = new ArtifactItem(); artifactItem.setGroupId("groupId1"); artifactItem.setArtifactId("artifactId"); artifactItem.setVersion("1.1.1"); Assert.assertFalse(artifactItem.isSameWithVersion(null)); ArtifactItem artifactItem1 = new ArtifactItem(); artifactItem1.setGroupId("groupId1"); artifactItem1.setArtifactId("artifactId"); artifactItem1.setVersion("1.1.1"); assertTrue(artifactItem.isSameWithVersion(artifactItem1)); artifactItem1.setVersion("2.2.2"); Assert.assertFalse(artifactItem.isSameWithVersion(artifactItem1)); artifactItem1.setVersion("*"); assertTrue(artifactItem.isSameWithVersion(artifactItem1)); } @Test public void testExecute() throws Exception { RepackageMojo repackageMojo = new RepackageMojo(); // 1) test war maven project packaging MavenProject mavenProject = new MavenProject(); mavenProject.setPackaging("war"); Field field = RepackageMojo.class.getDeclaredField("mavenProject"); field.setAccessible(true); field.set(repackageMojo, mavenProject); repackageMojo.execute(); // 2) test pom maven project packaging mavenProject.setPackaging("pom"); repackageMojo.execute(); // 3) test arkClassifier equals bizClassifier field = RepackageMojo.class.getDeclaredField("arkClassifier"); field.setAccessible(true); field.set(repackageMojo, "aaa"); field = RepackageMojo.class.getDeclaredField("bizClassifier"); field.setAccessible(true); field.set(repackageMojo, "aaa"); mavenProject.setPackaging("jar"); repackageMojo.execute(); // 4) test arkClassifier not equals bizClassifier field = RepackageMojo.class.getDeclaredField("bizClassifier"); field.setAccessible(true); field.set(repackageMojo, "bbb"); field = RepackageMojo.class.getDeclaredField("skip"); field.setAccessible(true); field.set(repackageMojo, true); repackageMojo.execute(); // 5) test complicated artifacts with excludes, excludeGroupIds, excludeArtifactIds, declaredMode=true, attach=true config field.set(repackageMojo, false); mavenProject.setProjectBuildingRequest(new DefaultProjectBuildingRequest()); PluginDescriptor pluginDescriptor = new PluginDescriptor(); pluginDescriptor.setVersion("2.0"); Map pluginContext = new HashMap<>(); pluginContext.put("pluginDescriptor", pluginDescriptor); repackageMojo.setPluginContext(pluginContext); field = RepackageMojo.class.getDeclaredField("declaredMode"); field.setAccessible(true); field.set(repackageMojo, true); DefaultArtifact artifact = new DefaultArtifact("group1", "artifact1", "1.0", "compile", "", null, new DefaultArtifactHandler()); artifact.setFile(com.alipay.sofa.ark.common.util.FileUtils.file(getClass().getClassLoader() .getResource("excludes.txt").getPath())); mavenProject.setArtifact(artifact); Set artifacts = new HashSet<>(); artifact = new DefaultArtifact("group1", "artifact2", "1.0", "compile", "", "jdk17", new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group1", "artifact3", "1.0", "compile", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group2", "artifact1", "1.0", "compile", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group2.a.b.b", "artifact4", "1.0", "compile", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group3.c", "artifact5", "1.0", "compile", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group3def", "artifact5", "1.0", "compile", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group4", "artifact1.g.h.g", "1.0", "compile", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group4", "artifact1.i", "1.0", "compile", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); artifact = new DefaultArtifact("group4", "artifact1gkl", "1.0", "compile", "", null, new DefaultArtifactHandler()); artifact.setFile(new File("./")); artifacts.add(artifact); mavenProject.setArtifacts(artifacts); Set excludeGroupIds = new LinkedHashSet<>(); excludeGroupIds.add("group2.a.*"); excludeGroupIds.add("group3d*"); excludeGroupIds.add("group3.c"); field = RepackageMojo.class.getDeclaredField("excludeGroupIds"); field.setAccessible(true); field.set(repackageMojo, excludeGroupIds); Set excludeArtifactIds = new LinkedHashSet<>(); excludeArtifactIds.add("artifact1.g.*"); excludeArtifactIds.add("artifact1gk*"); excludeArtifactIds.add("artifact1.i"); field = RepackageMojo.class.getDeclaredField("excludeArtifactIds"); field.setAccessible(true); field.set(repackageMojo, excludeArtifactIds); Set excludes = new LinkedHashSet<>(); excludes.add("group1:artifact3:1.0"); excludes.add("group1:artifact2:1.0:17"); excludes.add("groupx:x:1.0"); field = RepackageMojo.class.getDeclaredField("excludes"); field.setAccessible(true); field.set(repackageMojo, excludes); field = RepackageMojo.class.getDeclaredField("attach"); field.setAccessible(true); field.set(repackageMojo, true); field = RepackageMojo.class.getDeclaredField("outputDirectory"); field.setAccessible(true); field.set(repackageMojo, new File("./")); field = RepackageMojo.class.getDeclaredField("outputDirectory"); field.setAccessible(true); field.set(repackageMojo, new File("./")); RepositorySystem repositorySystem = mock(RepositorySystem.class); field = RepackageMojo.class.getDeclaredField("repositorySystem"); field.setAccessible(true); field.set(repackageMojo, repositorySystem); MavenSession mavenSession = mock(MavenSession.class); when(mavenSession.getProjectBuildingRequest()).thenReturn( new DefaultProjectBuildingRequest()); MavenExecutionRequest mavenExecutionRequest = new DefaultMavenExecutionRequest(); mavenExecutionRequest.setUserSettingsFile(new File("./")); mavenExecutionRequest.setGlobalSettingsFile(new File("./")); when(mavenSession.getRequest()).thenReturn(mavenExecutionRequest); Settings settings = new Settings(); settings.setInteractiveMode(true); settings.setActiveProfiles(new ArrayList<>()); when(mavenSession.getSettings()).thenReturn(settings); field = RepackageMojo.class.getDeclaredField("mavenSession"); field.setAccessible(true); field.set(repackageMojo, mavenSession); MavenProject parentMavenProject = new MavenProject(); parentMavenProject.setFile(new File("./a")); mavenProject.setParent(parentMavenProject); setProperty("maven.home", "./"); Exception exception = null; try { repackageMojo.execute(); } catch (MojoExecutionException mee) { exception = mee; } assertNotNull(exception); // 6) test with declaredMode=false exception = null; field = RepackageMojo.class.getDeclaredField("declaredMode"); field.setAccessible(true); field.set(repackageMojo, false); try { repackageMojo.execute(); } catch (MojoExecutionException mee) { exception = mee; } assertNotNull(exception); // 7) test updateArtifact with skipArkExecutable=false MavenProjectHelper mavenProjectHelper = mock(MavenProjectHelper.class); field = RepackageMojo.class.getDeclaredField("projectHelper"); field.setAccessible(true); field.set(repackageMojo, mavenProjectHelper); Method method = RepackageMojo.class.getDeclaredMethod("updateArtifact", File.class, File.class); method.setAccessible(true); method.invoke(repackageMojo, new File("./"), new File("./")); // 8) test updateArtifact with skipArkExecutable=true and keepArkBizJar=false field = RepackageMojo.class.getDeclaredField("skipArkExecutable"); field.setAccessible(true); field.set(repackageMojo, true); field = RepackageMojo.class.getDeclaredField("keepArkBizJar"); field.setAccessible(true); field.set(repackageMojo, true); method.invoke(repackageMojo, new File("./"), new File("./")); } @Test public void testInnerModelClass() throws InvocationTargetException, IllegalAccessException { copyProperties(new ExcludeConfig(), new ExcludeConfig()); copyProperties(new ExcludeConfigResponse(), new ExcludeConfigResponse()); assertEquals("", getClassifier()); } @Test public void testGetDeclaredLibrariesWhiteList() throws URISyntaxException, IOException { RepackageMojo repackageMojo = new RepackageMojo(); ReflectionUtils.setField("declaredMode", repackageMojo, true); ReflectionUtils.setField("baseDir", repackageMojo, CommonUtils.getResourceFile("baseDir")); Set whitelist = repackageMojo.getDeclaredLibrariesWhitelist(); Set res = whitelist.stream().map(it->it.getGroupId()+":"+it.getArtifactId()).collect(Collectors.toSet()); assertTrue(res.contains("com.ark.yml:ark-common-yml")); assertTrue(res.contains("com.biz.yml:biz-common-yml")); assertTrue(res.contains("com.biz:biz-common")); assertTrue(res.contains("com.ark:ark-common")); } } ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/test/java/com/alipay/sofa/ark/boot/mojo/utils/ParseUtilsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo.utils; import com.alipay.sofa.ark.boot.mojo.CommonUtils; import org.junit.Test; import org.yaml.snakeyaml.Yaml; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URISyntaxException; import java.util.Map; import java.util.Set; import static com.alipay.sofa.ark.boot.mojo.utils.ParseUtils.getBooleanWithDefault; import static com.alipay.sofa.ark.spi.constant.Constants.DECLARED_LIBRARIES_WHITELIST; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * @author lianglipeng.llp@alibaba-inc.com * @version $Id: ParseUtilsTest.java, v 0.1 2024年08月23日 10:55 立蓬 Exp $ */ public class ParseUtilsTest { @Test public void testGetStringSetForYaml() throws URISyntaxException { Map yml = (Map) loadYaml(); Set excludes = ParseUtils.getStringSet(yml, "excludes"); Set whitelist = ParseUtils.getStringSet(yml, DECLARED_LIBRARIES_WHITELIST); assertTrue(excludes.contains("org.apache.commons:commons-lang3-yml")); assertTrue(excludes.contains("commons-beanutils:commons-beanutils-yml")); assertTrue(whitelist.contains("com.biz.yml:biz-common-yml")); assertTrue(whitelist.contains("com.ark.yml:ark-common-yml")); } @Test public void testGetBooleanWithDefault() throws URISyntaxException { Map yml = (Map) loadYaml(); assertFalse(getBooleanWithDefault(yml, "excludeWithIndirectDependencies", true)); assertTrue(getBooleanWithDefault(yml, "aaa", true)); } private Object loadYaml() throws URISyntaxException { File yml = CommonUtils.getResourceFile("baseDir/conf/ark/bootstrap.yml"); try (FileInputStream fis = new FileInputStream(yml)) { Yaml yaml = new Yaml(); return yaml.load(fis); } catch (FileNotFoundException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } } ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/test/resources/baseDir/conf/ark/bootstrap.properties ================================================ # excludes config {groupId:artifactId} {groupId:artifactId:version} or {groupId:artifactId:version:classifier}, split by ',' excludes=org.apache.commons:commons-lang3,commons-beanutils:commons-beanutils # excludeGroupIds config ${groupId}, split by ',' excludeGroupIds=org.springframework # excludeArtifactIds config ${artifactId}, split by ',' excludeArtifactIds=sofa-ark-spi # includes config {groupId:artifactId} {groupId:artifactId:version} or {groupId:artifactId:version:classifier}, split by ',' includes=com.alipay.sofa:sofa-ark-all # includeGroupIds config ${groupId}, split by ',' includeGroupIds=com.alipay.sofa # includeArtifactIds config ${artifactId}, split by ',' includeArtifactIds=sofa-ark-all # declared libraries whitelist config {groupId:artifactId}, split by ',' declared.libraries.whitelist=com.ark:ark-common,com.biz:biz-common excludeWithIndirectDependencies=false ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/test/resources/baseDir/conf/ark/bootstrap.yml ================================================ # excludes 中配置 groupId:artifactId} {groupId:artifactId:version} 或 {groupId:artifactId:version:classifier}, 不同依赖以 - 隔开 # excludeGroupIds 中配置 ${groupId}, 不同依赖以 - 隔开 # excludeArtifactIds 中配置 ${artifactId}, 不同依赖以 - 隔开 excludes: - org.apache.commons:commons-lang3-yml - commons-beanutils:commons-beanutils-yml excludeGroupIds: - org.springframework-yml excludeArtifactIds: - sofa-ark-spi-yml includes: - com.alipay.sofa:sofa-ark-all-yml includeGroupIds: - com.alipay.sofa includeArtifactIds: - sofa-ark-all-yml # declaredArtifactIds 中配置 ${artifactId}, 不同依赖以 - 隔开 declared: libraries: whitelist: - com.ark.yml:ark-common-yml - com.biz.yml:biz-common-yml excludeWithIndirectDependencies: false ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/test/resources/dependency-tree-mock.txt ================================================ --- dependency:3.6.0:tree (default-cli) @ sofa-ark-springboot-starter --- [INFO] com.alipay.sofa:sofa-ark-springboot-starter:jar:2.2.4-SNAPSHOT [INFO] +- org.springframework.boot:spring-boot:jar:2.7.14:provided [INFO] | +- org.springframework:spring-core:jar:5.3.29:provided [INFO] | | \- org.springframework:spring-jcl:jar:5.3.29:provided [INFO] | \- org.springframework:spring-context:jar:5.3.29:provided ================================================ FILE: sofa-ark-parent/support/ark-maven-plugin/src/test/resources/excludes.txt ================================================ excludes=tracer-core:3.0.10 excludes=tracer-core:3.0.11 excludeGroupIds=com.alipay.sofa excludeGroupIds=org.springframework.boot excludeArtifactIds=tracer-core excludeArtifactIds=tracer-extension ================================================ FILE: sofa-ark-parent/support/ark-plugin-gradle-plugin/README.md ================================================ # Ark Plugin Gradle打包插件使用 `sofa-ark-plugin-gradle-plugin`模块是Ark Plugin打包工具的Gradle版本实现,和Maven打包工具`sofa-ark-plugin-maven-plugin`有同样的功能。在后文,使用**Gradle插件**来指代`sofa-ark-plugin-gradle-plugin`。 本小节会对**Gradle插件**进行介绍,随后会展示如何使用**Gradle插件**打包Ark Plugin。 ### 配置项 **Gradle插件**提供了和Maven基本相同的配置项,在使用上略有不同,想要使用配置项,需要在打包的项目build.gradle使用arkPlugin: ``` arkPlugin{ //具体配置项 } ``` - activator使用 ``` activator = 'sample.activator.SamplePluginActivator' ``` - excludes使用 ``` excludes = ['com.fasterxml.jackson.module:jackson-module-parameter-names', 'org.example:common'] ``` - exported使用 ``` exported { packages = [ 'com.alipay.sofa.ark.sample.common' ] classes = [ 'sample.facade.SamplePluginService' ] resource = [ 'META-INF/spring/bean.xml' ] } ``` ### 引入 引入方式可以分为两种: 1. 本地引入 2. 远程引入(正在申请) 本地引入的方式是将Gradle插件发布到本地的Maven仓库中,之后使用Gradle进行加载。 #### 将Gradle插件发布到本地仓库 1. 在**Gradle插件**的build.gradle的plugin解开注释,如下所示: ``` plugins { id 'java' id 'java-gradle-plugin' // 本地调试用,发布到maven id 'maven-publish' } ``` 2. 配置publish 在build.gradle中增加如下内容: ``` publishing { // 配置Plugin GAV publications { maven(MavenPublication) { groupId = group artifactId = 'plugin' version = version from components.java } } // 配置仓库地址 repositories { maven { url file('E:/repo') } } } ``` 点击在IDEA的右侧Gradle中的 Tasks > publishing > publish 将插件发布到本地仓库。 #### 在本地项目中引入 1. 在Gradle项目根目录的setting.gradle中设置pluginManagement ``` pluginManagement { repositories { // 指定maven仓库 maven { url "file:///E:/repo" } } } ``` 2. 在需要打包的项目中的build.gradle进行如下的配置 ``` buildscript { repositories { // ... maven { url "file:///E:/repo" } } dependencies { classpath("sofa.ark.gradle:sofa-ark-gradle-plugin:1.1") } } plugins { id 'sofa.ark.gradle.plugin' version "1.1" } ``` 3. 增加配置 在需要打包的项目中的build.gradle创建配置项: ``` arkPlugin{ outputDirectory = layout.buildDirectory.dir("custom-output") activator = 'sample.activator.SamplePluginActivator' excludes = ['com.fasterxml.jackson.module:jackson-module-parameter-names'] shades = [ 'org.example:common:1.0' ] exported { packages = [ 'com.alipay.sofa.ark.sample.common' ] classes = [ 'sample.facade.SamplePluginService' ] } } ``` 使用Gradle刷新后,如果一切正常,会在IDEA右侧Gradle任务列表中出现arkPluginJar,具体如下: Tasks > build > arkPluginJar,点击arkPluginJa执行,会在指定的outputDirectory中输出Ark Plugin包。 ================================================ FILE: sofa-ark-parent/support/ark-plugin-gradle-plugin/build.gradle ================================================ plugins { id 'java' id 'java-gradle-plugin' id 'maven-publish' } ext { arkPluginGradlePluginId = 'sofa-ark-plugin-gradle-plugin' } group = 'com.alipay.sofa' version = '1.0.0' sourceCompatibility = '1.8' gradlePlugin { plugins { DependenciesPlugin{ id = arkPluginGradlePluginId implementationClass = 'com.alipay.sofa.ark.boot.mojo.ArkPlugin' } } } repositories { mavenLocal() mavenCentral() } dependencies { implementation 'org.apache.commons:commons-compress:1.26.0' testImplementation 'junit:junit:4.13.1' testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.8.2") } publishing { publications { maven(MavenPublication) { from components.java groupId = project.group artifactId = arkPluginGradlePluginId version = project.version } } // publish to local maven repository repositories { maven { mavenLocal() } } } tasks.named('test') { useJUnitPlatform() } ================================================ FILE: sofa-ark-parent/support/ark-plugin-gradle-plugin/src/main/java/com/alipay/sofa/ark/boot/mojo/ArkPlugin.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import org.gradle.api.Plugin; import org.gradle.api.Project; public class ArkPlugin implements Plugin { @Override public void apply(Project project) { project.getExtensions().create("arkPlugin", ArkPluginExtension.class, project); project.getTasks().register("arkPluginJar", ArkPluginJarTask.class, task -> { task.setGroup("build"); task.setDescription("Generates an Ark plugin JAR file"); } ); } } ================================================ FILE: sofa-ark-parent/support/ark-plugin-gradle-plugin/src/main/java/com/alipay/sofa/ark/boot/mojo/ArkPluginCopyAction.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Set; import java.util.zip.CRC32; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.apache.commons.compress.archivers.zip.UnixStat; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.gradle.api.internal.file.CopyActionProcessingStreamAction; import org.gradle.api.internal.file.copy.CopyAction; import org.gradle.api.internal.file.copy.CopyActionProcessingStream; import org.gradle.api.internal.file.copy.FileCopyDetailsInternal; import org.gradle.api.tasks.WorkResult; import org.gradle.api.tasks.WorkResults; public class ArkPluginCopyAction implements CopyAction { private final File jarFile; private final Set shadeFiles; public ArkPluginCopyAction(File jarFile, Set shadeFiles) { this.jarFile = jarFile; this.shadeFiles = shadeFiles; } @Override public WorkResult execute(CopyActionProcessingStream stream) { try (ZipArchiveOutputStream zipStream = new ZipArchiveOutputStream(jarFile)) { zipStream.setEncoding(String.valueOf(StandardCharsets.UTF_8)); StreamAction action = new StreamAction(zipStream, shadeFiles); stream.process(action); return WorkResults.didWork(true); } catch (IOException e) { throw new RuntimeException("Failed to create JAR file", e); } } private static class StreamAction implements CopyActionProcessingStreamAction { private final ZipArchiveOutputStream zipStream; private final Set shadeFiles; StreamAction(ZipArchiveOutputStream zipStream, Set shadeFiles) { this.zipStream = zipStream; this.shadeFiles = shadeFiles; } @Override public void processFile(FileCopyDetailsInternal details) { try { if (details.isDirectory()) { addDirectory(details); } else if (shadeFiles.contains(details.getFile())) { addShadeFileContents(details.getFile()); } else { addFile(details); } } catch (IOException e) { throw new RuntimeException("Failed to add file to JAR: " + details.getPath(), e); } } private void addDirectory(FileCopyDetailsInternal details) throws IOException { ZipArchiveEntry entry = createEntry(details); zipStream.putArchiveEntry(entry); zipStream.closeArchiveEntry(); } private void addShadeFileContents(File shadeFile) throws IOException { try (ZipFile zipFile = new ZipFile(shadeFile)) { zipFile.stream() .filter(this::shouldProcessEntry) .forEach(entry -> processShadeEntry(zipFile, entry)); } } private void addFile(FileCopyDetailsInternal details) throws IOException { ZipArchiveEntry entry = createEntry(details); String path = details.getRelativePath().getPathString(); if (path.startsWith("lib")) { try (InputStream inputStream = details.open()) { CrcAndSize crcAndSize = new CrcAndSize(inputStream); crcAndSize.setUpStoredEntry(entry); } catch (Exception e) { throw new IOException("please check this jar file"); } } zipStream.putArchiveEntry(entry); details.copyTo(zipStream); zipStream.closeArchiveEntry(); } private ZipArchiveEntry createEntry(FileCopyDetailsInternal details){ String path = details.isDirectory() ? details.getRelativePath().getPathString() + '/' : details.getRelativePath().getPathString(); ZipArchiveEntry entry = new ZipArchiveEntry(path); entry.setTime(details.getLastModified()); int unixMode = details.getMode() | (details.isDirectory() ? UnixStat.DIR_FLAG : UnixStat.FILE_FLAG); entry.setUnixMode(unixMode); return entry; } private boolean shouldProcessEntry(ZipEntry entry) { return !"META-INF/MANIFEST.MF".equals(entry.getName()); } private void processShadeEntry(ZipFile zipFile, ZipEntry entry) { try { ZipArchiveEntry newEntry = createNewEntry(entry); if (entry.isDirectory()) { addDirectoryEntry(newEntry); } else { addFileEntry(zipFile, entry, newEntry); } } catch (IOException e) { throw new RuntimeException("Failed to process shade entry: " + entry.getName(), e); } } private ZipArchiveEntry createNewEntry(ZipEntry entry) { ZipArchiveEntry newEntry = new ZipArchiveEntry(entry.getName()); newEntry.setTime(entry.getTime()); newEntry.setUnixMode(UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM); return newEntry; } private void addDirectoryEntry(ZipArchiveEntry entry) throws IOException { zipStream.putArchiveEntry(entry); zipStream.closeArchiveEntry(); } private void addFileEntry(ZipFile zipFile, ZipEntry entry, ZipArchiveEntry newEntry) throws IOException { zipStream.putArchiveEntry(newEntry); try (InputStream inputStream = zipFile.getInputStream(entry)) { copy(inputStream, zipStream); } zipStream.closeArchiveEntry(); } private void copy(InputStream in, OutputStream out) throws IOException { byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } } } /** * Data holder for CRC and Size. */ private static class CrcAndSize { private static final int BUFFER_SIZE = 32 * 1024; private final CRC32 crc = new CRC32(); private long size; CrcAndSize(InputStream inputStream) throws IOException { try { load(inputStream); } finally { inputStream.close(); } } private void load(InputStream inputStream) throws IOException { byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { this.crc.update(buffer, 0, bytesRead); this.size += bytesRead; } } void setUpStoredEntry(ZipArchiveEntry entry) { entry.setSize(this.size); entry.setCompressedSize(this.size); entry.setCrc(this.crc.getValue()); entry.setMethod(ZipEntry.STORED); } } } ================================================ FILE: sofa-ark-parent/support/ark-plugin-gradle-plugin/src/main/java/com/alipay/sofa/ark/boot/mojo/ArkPluginExtension.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.SetProperty; abstract public class ArkPluginExtension { public ArkPluginExtension(Project project){ getPriority().convention(project.provider(() -> "100")); getOutputDirectory().convention(project.getLayout().getBuildDirectory().dir("libs")); getPluginName().convention(project.getName()); getDescription().convention(""); getActivator().convention(""); } abstract public SetProperty getShades(); abstract public SetProperty getExcludeArtifactIds(); abstract public SetProperty getExcludeGroupIds(); abstract public SetProperty getExcludes(); abstract public Property getActivator(); abstract public Property getPriority(); abstract public Property getPluginName(); abstract public Property getDescription(); abstract public DirectoryProperty getOutputDirectory(); abstract public Property getAttach(); private final ImportedConfig imported = new ImportedConfig(); private final ExportedConfig exported = new ExportedConfig(); public ImportedConfig getImported() { return imported; } public void imported(Action action) { action.execute(imported); } public ExportedConfig getExported() { return exported; } public void exported(Action action) { action.execute(exported); } public static class ImportedConfig extends BaseConfig{ } public static class ExportedConfig extends BaseConfig{ } } ================================================ FILE: sofa-ark-parent/support/ark-plugin-gradle-plugin/src/main/java/com/alipay/sofa/ark/boot/mojo/ArkPluginJarTask.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.zip.ZipFile; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ExcludeRule; import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.internal.file.copy.CopyAction; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskAction; import org.gradle.jvm.tasks.Jar; public class ArkPluginJarTask extends Jar { private final ArkPluginExtension arkPluginExtension; private final Set filteredArtifacts; private final Set conflictArtifacts; private final Set shadeFiles = new HashSet<>(); public ArkPluginJarTask(){ super(); Project project = getProject(); arkPluginExtension = project.getExtensions().findByType(ArkPluginExtension.class); configureDestination(); configurePluginJar(); from(project.getTasks().getByName("classes")); Set allArtifacts = getProjectArtifacts(); filteredArtifacts = filterArtifacts1(allArtifacts); conflictArtifacts = filterConflictArtifacts(filteredArtifacts); configureSourceSet(project); configureArtifacts(); handleConflictArtifacts(conflictArtifacts); configureManifest(project); addArkPluginMark(); } private void configurePluginJar(){ getArchiveFileName().set(getProject().provider(() -> { String pluginName = arkPluginExtension.getPluginName().get(); return pluginName + ".jar"; })); } private void configureArtifacts() { into("lib", copySpec -> { copySpec.from(getProject().provider(this::getFilteredArtifactFiles)); copySpec.rename(this::renameArtifactIfConflict); }); into("", copySpec -> { copySpec.from(getProject().provider(() -> shadeFiles)); }); } private String renameArtifactIfConflict(String fileName) { ResolvedArtifact artifact = findArtifactByFileName(fileName); if (artifact != null && conflictArtifacts.contains(artifact)) { return artifact.getModuleVersion().getId().getGroup() + "-" + fileName; } return fileName; } private ResolvedArtifact findArtifactByFileName(String fileName) { return filteredArtifacts.stream() .filter(artifact -> artifact.getFile().getName().equals(fileName)) .findFirst() .orElse(null); } private Set getFilteredArtifactFiles() { Set shadeNames = arkPluginExtension.getShades().get().stream() .map(this::getShadeFileName) .collect(Collectors.toSet()); return filteredArtifacts.stream() .filter(artifact -> { boolean isShade = shadeNames.contains(artifact.getFile().getName()); if (isShade) { shadeFiles.add(artifact.getFile()); } return !isShade; }) .map(ResolvedArtifact::getFile) .filter(this::isZip) .collect(Collectors.toSet()); } private String getShadeFileName(String shade) { String[] parts = shade.split(":"); if (parts.length != 3) { throw new IllegalArgumentException("Invalid shade format: " + shade); } String name = parts[1]; String version = parts[2]; return name + "-" + version + ".jar"; } private void configureManifest(Project project){ Provider> manifestAttributes = project.provider(() -> { Map attributes = new HashMap<>(); attributes.put("Manifest-Version", "1.0"); attributes.put("Ark-Plugin-Name", arkPluginExtension.getPluginName().get()); attributes.put("Ark-Plugin-Version", project.getVersion().toString()); attributes.put("groupId", project.getGroup().toString()); attributes.put("artifactId", project.getName()); attributes.put("version", project.getVersion().toString()); attributes.put("priority", arkPluginExtension.getPriority().get()); attributes.put("pluginName", arkPluginExtension.getPluginName().get()); attributes.put("description", arkPluginExtension.getDescription().get()); attributes.put("activator", arkPluginExtension.getActivator().get()); attributes.putAll(arkPluginExtension.getImported().toAttributes("import")); attributes.putAll(arkPluginExtension.getExported().toAttributes("export")); return attributes; }); getManifest().attributes(manifestAttributes.get()); } private void configureArtifacts(Project project, Set filteredArtifacts){ from(project.provider(() -> { return filteredArtifacts.stream() .map(ResolvedArtifact::getFile) .collect(Collectors.toSet()); })); } private void configureDestination(){ getDestinationDirectory().set(arkPluginExtension.getOutputDirectory()); } private void configureSourceSet(Project project){ SourceSet mainSourceSet = project.getExtensions() .getByType(JavaPluginExtension.class) .getSourceSets() .getByName(SourceSet.MAIN_SOURCE_SET_NAME); from(mainSourceSet.getOutput()); } private void configureCopySpec(Project project) { from(project.provider(() -> { Configuration runtimeClasspath = project.getConfigurations().getByName("runtimeClasspath"); Set artifacts = runtimeClasspath.getResolvedConfiguration().getResolvedArtifacts(); return filterArtifacts(artifacts); })); } private Set filterArtifacts1(Set artifacts) { return artifacts.stream() .filter(this::shouldIncludeArtifact) .collect(Collectors.toSet()); } private Set filterArtifacts(Set artifacts) { return artifacts.stream() .filter(this::shouldIncludeArtifact) .map(ResolvedArtifact::getFile) .collect(Collectors.toSet()); } private boolean shouldIncludeArtifact(ResolvedArtifact artifact) { String groupId = artifact.getModuleVersion().getId().getGroup(); String artifactId = artifact.getName(); String gav = groupId + ":" + artifactId ; if (this.arkPluginExtension.getExcludes().get().contains(gav)) { return false; } if (this.arkPluginExtension.getExcludeGroupIds().get().contains(groupId)) { return false; } if (this.arkPluginExtension.getExcludeArtifactIds().get().contains(artifactId)) { return false; } return true; } private Set getProjectArtifacts() { Configuration configuration = getProject().getConfigurations().getByName("runtimeClasspath"); return configuration.getResolvedConfiguration().getResolvedArtifacts(); } private boolean isZip(File file) { try (ZipFile zipFile = new ZipFile(file)) { return true; } catch (Exception e) { return false; } } @Override protected CopyAction createCopyAction() { File jarFile = getArchiveFile().get().getAsFile(); return new ArkPluginCopyAction(jarFile, shadeFiles); } @TaskAction private void action(){ super.copy(); } private void addArkPluginMark() { String markContent = "this is plugin mark"; String markPath = "com/alipay/sofa/ark/plugin"; from(getProject().provider(() -> { try { File tempFile = File.createTempFile("mark", null); tempFile.deleteOnExit(); Files.write(tempFile.toPath(), markContent.getBytes(StandardCharsets.UTF_8)); return tempFile; } catch (IOException e) { throw new RuntimeException("Failed to create mark file", e); } }), copySpec -> { copySpec.into(markPath); copySpec.rename(fileName -> "mark"); }); } protected Set filterConflictArtifacts(Set artifacts) { Project project = getProject(); String projectArtifactId = project.getName(); Map existArtifacts = new HashMap<>(); existArtifacts.put(projectArtifactId, null); // ResolvedArtifact Set conflictArtifacts = new HashSet<>(); for (ResolvedArtifact artifact : artifacts) { String artifactId = artifact.getName(); if (existArtifacts.containsKey(artifactId)) { conflictArtifacts.add(artifact); ResolvedArtifact existingArtifact = existArtifacts.get(artifactId); if (existingArtifact != null) { conflictArtifacts.add(existingArtifact); } } else { existArtifacts.put(artifactId, artifact); } } return conflictArtifacts; } private void handleConflictArtifacts(Set conflictArtifacts) { for (ResolvedArtifact conflictArtifact : conflictArtifacts) { getLogger().warn("Conflict artifact found: {}:{}:{}", conflictArtifact.getModuleVersion().getId().getGroup(), conflictArtifact.getName(), conflictArtifact.getModuleVersion().getId().getVersion()); } } } ================================================ FILE: sofa-ark-parent/support/ark-plugin-gradle-plugin/src/main/java/com/alipay/sofa/ark/boot/mojo/BaseConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; abstract public class BaseConfig { private List packages = new ArrayList<>(); private List classes = new ArrayList<>(); private List resources = new ArrayList<>(); public List getPackages() { return packages; } public void setPackages(List packages) { this.packages = packages; } public List getClasses() { return classes; } public void setClasses(List classes) { this.classes = classes; } public List getResources() { return resources; } public void setResources(List resources) { this.resources = resources; } public Map toAttributes(String prefix) { Map attributes = new HashMap<>(); attributes.put(prefix + "-packages", packages != null ? String.join(",", packages) : ""); attributes.put(prefix + "-classes", classes != null ? String.join(",", classes) : ""); attributes.put(prefix + "-resources", resources != null ? String.join(",", resources) : ""); return attributes; } } ================================================ FILE: sofa-ark-parent/support/ark-plugin-gradle-plugin/src/test/java/com/alipay/sofa/ark/boot/mojo/ArkPluginExtensionTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.boot.mojo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.File; import org.gradle.api.Project; import org.gradle.testfixtures.ProjectBuilder; import org.junit.Before; import org.junit.Test; public class ArkPluginExtensionTest { private Project project; private ArkPluginExtension extension; @Before public void setup() { project = ProjectBuilder.builder().withName("test-project").build(); project.getPluginManager().apply("sofa-ark-plugin-gradle-plugin"); extension = project.getExtensions().getByType(ArkPluginExtension.class); } @Test public void testDefaultValues() { assertEquals("100", extension.getPriority().get()); assertEquals("test-project", extension.getPluginName().get()); assertEquals("", extension.getDescription().get()); assertEquals("", extension.getActivator().get()); assertTrue(extension.getOutputDirectory().get().getAsFile().getPath().endsWith("build" + File.separator + "libs")); } @Test public void testSetAndGetValues() { extension.getPriority().set("200"); extension.getPluginName().set("test-plugin"); extension.getDescription().set("Test description"); extension.getActivator().set("com.example.TestActivator"); extension.getAttach().set(true); assertEquals("200", extension.getPriority().get()); assertEquals("test-plugin", extension.getPluginName().get()); assertEquals("Test description", extension.getDescription().get()); assertEquals("com.example.TestActivator", extension.getActivator().get()); assertTrue(extension.getAttach().get()); } } ================================================ FILE: sofa-ark-parent/support/ark-plugin-maven-plugin/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-plugin-maven-plugin` **Packaging**: `maven-plugin` **Goal**: `ark-plugin` This Maven plugin packages multiple JARs into a single Ark Plugin. Plugins provide class-isolated shared capabilities that can be used by business modules. ## Purpose - Create Ark Plugin archives from Maven dependencies - Configure plugin class import/export settings - Set plugin priority and activator ## Key Classes ### `ArkPluginMojo` Main Maven Mojo bound to `package` phase: - Aggregates multiple JARs into a single plugin - Configures classloader isolation settings Key configuration parameters: - `activator` - Plugin activator class (implements `PluginActivator`) - `excludeGroupIds` / `excludeArtifactIds` - Dependencies to exclude from plugin - `exportPackages` - Packages to export for other plugins/biz to use - `exportClasses` - Specific classes to export - `importPackages` - Packages to import from other plugins - `importClasses` - Specific classes to import - `exportResources` - Resources to export - `importResources` - Resources to import - `priority` - Plugin startup priority (higher starts earlier) ### `ImportConfig` Configuration for imported packages/classes/resources. ### `ExportConfig` Configuration for exported packages/classes/resources. ### `LinkedProperties` / `LinkedManifest` / `LinkedAttributes` Utility classes for maintaining order in manifest entries. ## Plugin Structure ``` plugin.jar ├── com/alipay/sofa/ark/plugin/ # Plugin metadata │ └── export.index # Export index ├── lib/ # Bundled JARs └── META-INF/MANIFEST.MF # Plugin metadata ``` ## Usage ```xml com.alipay.sofa sofa-ark-plugin-maven-plugin ark-plugin com.example.MyPluginActivator com.example.api 1000 ``` ## Dependencies - Maven Core/Plugin APIs - `sofa-ark-common` - Utilities - `sofa-ark-tools` - Repackaging utilities ================================================ FILE: sofa-ark-parent/support/ark-plugin-maven-plugin/META-INF/MANIFEST.MF ================================================ Manifest-Version: 1.0 groupId: a artifactId: b version: c priority: 10 pluginName: xxx description: yyy activator: import-packages: import-classes: import-resources: export-mode: export-packages: export-classes: export-resources: ================================================ FILE: sofa-ark-parent/support/ark-plugin-maven-plugin/pom.xml ================================================ sofa-ark-support com.alipay.sofa ${sofa.ark.version} 4.0.0 sofa-ark-plugin-maven-plugin ${project.groupId}:${project.artifactId} maven-plugin com.alipay.sofa sofa-ark-spi com.alipay.sofa sofa-ark-common com.alipay.sofa sofa-ark-tools commons-io commons-io org.apache.maven maven-core org.apache.maven.plugin-tools maven-plugin-annotations org.apache.maven maven-archiver org.apache.maven.shared maven-common-artifact-filters org.codehaus.plexus plexus-component-annotations org.codehaus.plexus plexus-utils org.apache.commons commons-lang3 org.mockito mockito-inline test junit junit test org.apache.maven.plugins maven-plugin-plugin true mojo-descriptor descriptor ================================================ FILE: sofa-ark-parent/support/ark-plugin-maven-plugin/src/main/java/com/alipay/sofa/ark/plugin/mojo/AbstractPropertiesConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin.mojo; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Properties; /** * @author qilong.zql * @since 0.1.0 */ public abstract class AbstractPropertiesConfig { public static final String KEY_MODE = "mode"; public static final String KEY_PACKAGES = "packages"; public static final String KEY_CLASSES = "classes"; public static final String KEY_RESOURCES = "resources"; public static final String KEY_EXPORT = "export"; public static final String KEY_IMPORT = "import"; public static final String KEY_SPLIT = "-"; public static final String VALUE_SPLIT = ","; protected String mode; /** * imported or exported packages config */ protected LinkedHashSet packages; /** * imported or exported classes config */ protected LinkedHashSet classes; /** * imported or exported class config */ protected LinkedHashSet resources; public void addClass(String className) { if (classes == null) { classes = new LinkedHashSet<>(); } classes.add(className); } public String getMode() { return mode; } public void setMode(String mode) { this.mode = mode; } public LinkedHashSet getPackages() { return packages; } public void setPackages(LinkedHashSet packages) { this.packages = packages; } public LinkedHashSet getClasses() { return classes; } public void setClasses(LinkedHashSet classes) { this.classes = classes; } /** * Getter method for property resources. * * @return property value of resources */ public LinkedHashSet getResources() { return resources; } public void setResources(LinkedHashSet resources) { this.resources = resources; } public static void storeKeyValuePair(Properties prop, String name, Collection value) { if (value == null) { value = new LinkedHashSet<>(); } prop.setProperty(name, join(value.iterator(), VALUE_SPLIT)); } public static String join(Iterator iterator, String separator) { if (separator == null) { separator = ""; } StringBuffer buf = new StringBuffer(256); while (iterator.hasNext()) { buf.append(iterator.next()); if (iterator.hasNext()) { buf.append(separator); } } return buf.toString(); } /** * Store user configuration * @param props */ public abstract void store(Properties props); } ================================================ FILE: sofa-ark-parent/support/ark-plugin-maven-plugin/src/main/java/com/alipay/sofa/ark/plugin/mojo/ArkPluginMojo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin.mojo; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.stream.Collectors; import com.alipay.sofa.ark.common.util.ClassUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.tools.ArtifactItem; import com.alipay.sofa.ark.tools.JarWriter; import com.alipay.sofa.ark.tools.Repackager; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.*; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectHelper; import org.codehaus.plexus.archiver.Archiver; import org.codehaus.plexus.archiver.ArchiverException; import org.codehaus.plexus.archiver.manager.ArchiverManager; import org.codehaus.plexus.archiver.manager.NoSuchArchiverException; import org.codehaus.plexus.archiver.zip.AbstractZipArchiver; /** * @author qilong.zql * @since 0.1.0 */ @Mojo(name = "ark-plugin", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME) public class ArkPluginMojo extends AbstractMojo { @Component protected MavenProject project; @Component protected ArchiverManager archiverManager; @Component protected MavenProjectHelper projectHelper; /** * The location of the generated ark plugin */ @Parameter(defaultValue = "${project.build.directory}", property = "sofa.ark.plugin.repository") protected File outputDirectory; /** * The location of sofa-ark-maven-plugin temporary file */ @Parameter(defaultValue = "${project.build.directory}/sofa-ark-maven-plugin") protected File workDirectory; /** * The configuration of ark plugin */ @Parameter(defaultValue = "${project.groupId}", readonly = true) protected String groupId; @Parameter(defaultValue = "${project.artifactId}", readonly = true) protected String artifactId; @Parameter(defaultValue = "${project.version}", readonly = true) protected String version; @Parameter(defaultValue = "${project.artifactId}") public String pluginName; @Parameter(defaultValue = " ") protected String description; @Parameter(defaultValue = "100", property = "sofa.ark.plugin.priority") protected Integer priority; @Parameter protected String activator; @Parameter protected ExportConfig exported; @Parameter protected ImportConfig imported; /** * Colon separated groupId, artifactId [and classifier] to exclude (exact match) */ @Parameter(defaultValue = "") protected LinkedHashSet excludes = new LinkedHashSet<>(); /** * list of groupId names to exclude (exact match). */ @Parameter(defaultValue = "") protected LinkedHashSet excludeGroupIds; /** * list of artifact names to exclude (exact match). */ @Parameter(defaultValue = "") protected LinkedHashSet excludeArtifactIds; /** * Colon separated groupId, artifactId, classifier(optional), version. */ @Parameter(defaultValue = "") protected LinkedHashSet shades = new LinkedHashSet<>(); /** * whether install ark-plugin to local maven repository, if 'true', * it will be installed when execute 'mvn install' or 'mvn deploy'; * default set 'true'. */ @Parameter(defaultValue = "true") private Boolean attach; /** * default ark plugin artifact classifier: empty */ @Parameter(defaultValue = "") private String classifier; /** * Export plugin project classes by default */ @Parameter(defaultValue = "true") protected Boolean exportPluginClass; private static final String ARCHIVE_MODE = "zip"; private static final String PLUGIN_SUFFIX = ".ark.plugin"; private static final String TEMP_PLUGIN_SUFFIX = ".ark.plugin.bak"; @SuppressWarnings("unchecked") @Override public void execute() throws MojoExecutionException { Archiver archiver; try { archiver = getArchiver(); } catch (NoSuchArchiverException e) { throw new MojoExecutionException(e.getMessage()); } if (!outputDirectory.exists()) { outputDirectory.mkdirs(); } String fileName = getFileName(); File destination = new File(outputDirectory, fileName); File tmpDestination = new File(outputDirectory, getTempFileName()); if (destination.exists()) { destination.delete(); } if (tmpDestination.exists()) { tmpDestination.delete(); } archiver.setDestFile(tmpDestination); Set artifacts = project.getArtifacts(); artifacts = filterExcludeArtifacts(artifacts); Set conflictArtifacts = filterConflictArtifacts(artifacts); addArkPluginArtifact(archiver, artifacts, conflictArtifacts); addArkPluginConfig(archiver); try { archiver.createArchive(); shadeJarIntoArkPlugin(destination, tmpDestination, artifacts); } catch (ArchiverException | IOException e) { throw new MojoExecutionException(e.getMessage()); } finally { tmpDestination.delete(); } if (isAttach()) { if (StringUtils.isEmpty(classifier)) { Artifact artifact = project.getArtifact(); artifact.setFile(destination); project.setArtifact(artifact); } else { projectHelper.attachArtifact(project, destination, classifier); } } } public void shadeJarIntoArkPlugin(File pluginFile, File tmpPluginFile, Set artifacts) throws IOException { Set shadeJars = new HashSet<>(); shadeJars.add(project.getArtifact()); for (Artifact artifact : artifacts) { if (isShadeJar(artifact)) { shadeJars.add(artifact); } } JarWriter writer = new JarWriter(pluginFile); JarFile tmpJarFile = new JarFile(tmpPluginFile); try { writer.writeEntries(tmpJarFile); for (Artifact jar : shadeJars) { writer.writeEntries(new JarFile(jar.getFile())); } } finally { writer.close(); tmpJarFile.close(); } } public LinkedHashSet getShades() { return shades; } public void setShades(LinkedHashSet shades) { this.shades = shades; } public void setProject(MavenProject project) { this.project = project; } public boolean isShadeJar(Artifact artifact) { for (String shade : getShades()) { ArtifactItem artifactItem = ArtifactItem.parseArtifactItemWithVersion(shade); if (!artifact.getGroupId().equals(artifactItem.getGroupId())) { continue; } if (!artifact.getArtifactId().equals(artifactItem.getArtifactId())) { continue; } if (!artifact.getVersion().equals(artifactItem.getVersion())) { continue; } if (!StringUtils.isEmpty(artifactItem.getClassifier()) && !artifactItem.getClassifier().equals(artifact.getClassifier())) { continue; } if (artifact.getArtifactId().equals(project.getArtifactId()) && artifact.getGroupId().equals(project.getGroupId())) { throw new RuntimeException("Can't shade jar-self."); } return true; } return false; } /** * put all dependencies together into archive * * @param archiver ark plugin archiver * @param dependencies all dependencies of ark plugin * @param conflicts dependencies whose jar name (artifact id) is conflict */ protected void addArkPluginArtifact(Archiver archiver, Set dependencies, Set conflicts) { for (Artifact artifact : dependencies) { if (Repackager.isZip(artifact.getFile())) { addArtifact(archiver, artifact, conflicts.contains(artifact)); } } } /** * add a artifact into archiver * * @param archiver archiver which represents a ark plugin * @param artifact artifact which will be put into archiver * @param artifactIdConflict whether artifact is conflicted, it will determine the final jar name. */ protected void addArtifact(Archiver archiver, Artifact artifact, boolean artifactIdConflict) { if (isShadeJar(artifact)) { return; } String destination = artifact.getFile().getName(); if (artifactIdConflict) { destination = artifact.getGroupId() + "-" + destination; } destination = "lib/" + destination; getLog().debug(" " + artifact + " => " + destination); archiver.addFile(artifact.getFile(), destination); } /** * compute conflict artifacts * * @param artifacts all dependencies of project * @return artifacts whose jar name (artifact id) is conflict */ protected Set filterConflictArtifacts(Set artifacts) { HashMap existArtifacts = new HashMap<>(Collections.singletonMap(project .getArtifact().getArtifactId(), project.getArtifact())); HashSet conflictArtifacts = new HashSet<>(); for (Artifact artifact : artifacts) { if (existArtifacts.containsKey(artifact.getArtifactId())) { conflictArtifacts.add(artifact); conflictArtifacts.add(existArtifacts.get(artifact.getArtifactId())); } else { existArtifacts.put(artifact.getArtifactId(), artifact); } } return conflictArtifacts; } /** * filter the excluded dependencies * * @param artifacts all dependencies of project * @return dependencies excluded the excludes config */ protected Set filterExcludeArtifacts(Set artifacts) { List excludeList = new ArrayList<>(); for (String exclude : excludes) { ArtifactItem item = ArtifactItem.parseArtifactItemIgnoreVersion(exclude); excludeList.add(item); } Set result = new LinkedHashSet<>(); for (Artifact e : artifacts) { boolean isExclude = false; for (ArtifactItem exclude : excludeList) { if (exclude.isSameIgnoreVersion(ArtifactItem.parseArtifactItem(e))) { isExclude = true; break; } } if (excludeGroupIds != null && excludeGroupIds.contains(e.getGroupId())) { isExclude = true; } if (excludeArtifactIds != null && excludeArtifactIds.contains(e.getArtifactId())) { isExclude = true; } if (!isExclude) { result.add(e); } } return result; } /** * create a zip archiver * * @return a un-compress zip archiver * @throws NoSuchArchiverException */ protected Archiver getArchiver() throws NoSuchArchiverException { Archiver archiver = archiverManager.getArchiver(ARCHIVE_MODE); ((AbstractZipArchiver) archiver).setCompress(false); return archiver; } /** * repackage jar name * * @return ark plugin name */ protected String getFileName() { return String.format("%s%s", pluginName, PLUGIN_SUFFIX); } protected String getTempFileName() { return String.format("%s%s", pluginName, TEMP_PLUGIN_SUFFIX); } /** * check whether install ark plugin install local repo * default true. * * @return whether install local repo */ protected boolean isAttach() { return attach; } /** * check whether to export plugin project * default true. * * @return whether to export plugin project */ protected boolean getExportPluginClass() { return exportPluginClass; } /** * generate ark.plugin configuration file * archive * @param archiver * @throws MojoExecutionException */ protected void addArkPluginConfig(Archiver archiver) throws MojoExecutionException { addManifest(archiver); addArkPluginMark(archiver); } private void addManifest(Archiver archiver) throws MojoExecutionException { LinkedProperties properties = new LinkedProperties(); properties.setProperty("groupId", groupId); properties.setProperty("artifactId", artifactId); properties.setProperty("version", version); properties.setProperty("priority", String.valueOf(priority)); properties.setProperty("pluginName", pluginName); properties.setProperty("description", description); properties.setProperty("activator", activator == null ? "" : activator); properties.putAll(collectArkPluginImport()); properties.putAll(collectArkPluginExport()); addArkPluginConfig(archiver, "META-INF/MANIFEST.MF", properties); } private Properties collectArkPluginExport() throws MojoExecutionException { Properties properties = new LinkedProperties(); if (exported == null) { exported = new ExportConfig(); } if (exportPluginClass) { Set projectClasses = findProjectClasses(); for (String projectClass : projectClasses) { if (!StringUtils.isEmpty(projectClass)) { exported.addClass(projectClass); } } } exported.store(properties); return properties; } private Set findProjectClasses() throws MojoExecutionException { try { // Accessing the target/classes directory where compiled classes are located File outputDirectory = new File(project.getBuild().getOutputDirectory()); // Ensure the directory exists if (outputDirectory.exists()) { Set classes = new HashSet<>(ClassUtils.collectClasses(outputDirectory)); classes = classes.stream().filter(className -> !className.equals(this.activator)).collect( Collectors.toSet()); return classes; } else { getLog().warn("Output directory does not exist!"); } return new HashSet<>(); } catch (IOException e) { throw new MojoExecutionException("Error finding compiled classes", e); } } private Properties collectArkPluginImport() { Properties properties = new LinkedProperties(); if (imported == null) { imported = new ImportConfig(); } imported.store(properties); return properties; } private void addArkPluginConfig(Archiver archiver, String path, LinkedProperties properties) throws MojoExecutionException { File file = new File(workDirectory.getPath() + File.separator + path); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } PrintStream outputStream = null; Manifest manifest = new LinkedManifest(); manifest.getMainAttributes().putValue("Manifest-Version", "1.0"); Enumeration enumeration = properties.keys(); while (enumeration.hasMoreElements()) { String key = (String) enumeration.nextElement(); manifest.getMainAttributes().putValue(key, properties.getProperty(key)); } try { outputStream = new PrintStream(file, "UTF-8"); manifest.write(outputStream); } catch (Exception ex) { throw new MojoExecutionException(ex.getMessage()); } finally { if (outputStream != null) { outputStream.close(); } } archiver.addFile(file, path); } private void addArkPluginMark(Archiver archiver) throws MojoExecutionException { addArkPluginConfig(archiver, Constants.ARK_PLUGIN_MARK_ENTRY, new LinkedProperties()); } } ================================================ FILE: sofa-ark-parent/support/ark-plugin-maven-plugin/src/main/java/com/alipay/sofa/ark/plugin/mojo/ExportConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin.mojo; import java.util.Arrays; import java.util.Collections; import java.util.Properties; /** * @author qilong.zql * @since 0.1.0 */ public class ExportConfig extends AbstractPropertiesConfig { @Override public void store(Properties prop) { storeKeyValuePair(prop, KEY_EXPORT + KEY_SPLIT + KEY_MODE, getMode() == null ? null : Collections.singletonList(getMode())); storeKeyValuePair(prop, KEY_EXPORT + KEY_SPLIT + KEY_PACKAGES, getPackages()); storeKeyValuePair(prop, KEY_EXPORT + KEY_SPLIT + KEY_CLASSES, getClasses()); storeKeyValuePair(prop, KEY_EXPORT + KEY_SPLIT + KEY_RESOURCES, getResources()); } } ================================================ FILE: sofa-ark-parent/support/ark-plugin-maven-plugin/src/main/java/com/alipay/sofa/ark/plugin/mojo/ImportConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin.mojo; import java.util.Properties; /** * @author qilong.zql * @since 0.1.0 */ public class ImportConfig extends AbstractPropertiesConfig { @Override public void store(Properties prop) { storeKeyValuePair(prop, KEY_IMPORT + KEY_SPLIT + KEY_PACKAGES, getPackages()); storeKeyValuePair(prop, KEY_IMPORT + KEY_SPLIT + KEY_CLASSES, getClasses()); storeKeyValuePair(prop, KEY_IMPORT + KEY_SPLIT + KEY_RESOURCES, getResources()); } } ================================================ FILE: sofa-ark-parent/support/ark-plugin-maven-plugin/src/main/java/com/alipay/sofa/ark/plugin/mojo/LinkedAttributes.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin.mojo; import java.io.DataOutputStream; import java.io.IOException; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.jar.Attributes; /** * @author qilong.zql * @since 0.1.0 */ public class LinkedAttributes extends Attributes { private LinkedHashMap linkedHashMap = new LinkedHashMap<>(11); @Override public Object put(Object name, Object value) { linkedHashMap.put(name, value); return super.put(name, value); } @Override public Object remove(Object name) { linkedHashMap.remove(name); return super.remove(name); } @Override public Set> entrySet() { return linkedHashMap.entrySet(); } public void writeMain(DataOutputStream out) throws IOException { // write out the *-Version header first, if it exists String vername = Name.MANIFEST_VERSION.toString(); String version = getValue(vername); if (version == null) { vername = Name.SIGNATURE_VERSION.toString(); version = getValue(vername); } if (version != null) { out.writeBytes(vername + ": " + version + "\r\n"); } // write out all attributes except for the version // we wrote out earlier Iterator it = entrySet().iterator(); while (it.hasNext()) { Map.Entry e = (Map.Entry) it.next(); String name = e.getKey().toString(); if ((version != null) && !(name.equalsIgnoreCase(vername))) { StringBuffer buffer = new StringBuffer(name); buffer.append(": "); String value = (String) e.getValue(); if (value != null) { byte[] vb = value.getBytes("UTF8"); value = new String(vb, 0, 0, vb.length); } buffer.append(value); buffer.append("\r\n"); LinkedManifest.make72Safe(buffer); out.writeBytes(buffer.toString()); } } out.writeBytes("\r\n"); } } ================================================ FILE: sofa-ark-parent/support/ark-plugin-maven-plugin/src/main/java/com/alipay/sofa/ark/plugin/mojo/LinkedManifest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin.mojo; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.jar.Attributes; import java.util.jar.Manifest; /** * @author qilong.zql * @since 0.1.0 */ public class LinkedManifest extends Manifest { private LinkedAttributes attr = new LinkedAttributes(); @Override public Attributes getMainAttributes() { return attr; } @Override public void write(OutputStream out) throws IOException { DataOutputStream dos = new DataOutputStream(out); // Write out the main attributes for the manifest attr.writeMain(dos); dos.flush(); } /** * Adds line breaks to enforce a maximum 72 bytes per line. */ public static void make72Safe(StringBuffer line) { int length = line.length(); if (length > 72) { int index = 70; while (index < length - 2) { line.insert(index, "\r\n "); index += 72; length += 3; } } } } ================================================ FILE: sofa-ark-parent/support/ark-plugin-maven-plugin/src/main/java/com/alipay/sofa/ark/plugin/mojo/LinkedProperties.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin.mojo; import java.util.*; /** * @author qilong.zql * @since 0.1.0 */ public class LinkedProperties extends Properties { private static final long serialVersionUID = 1L; private LinkedHashMap linkedHashMap = new LinkedHashMap<>(20); @Override public synchronized Object put(Object key, Object value) { linkedHashMap.put(key, value); return super.put(key, value); } @Override public synchronized Object remove(Object key) { linkedHashMap.remove(key); return super.remove(key); } @Override public synchronized Enumeration keys() { return Collections.enumeration(linkedHashMap.keySet()); } @Override public Set> entrySet() { return linkedHashMap.entrySet(); } @Override public synchronized void putAll(Map t) { for (Map.Entry e : t.entrySet()) put(e.getKey(), e.getValue()); } } ================================================ FILE: sofa-ark-parent/support/ark-plugin-maven-plugin/src/main/resources/META-INF/maven/plugin.xml ================================================ sofa-ark-plugin-maven-plugin A light-weight, java based classloader-isolated framework open-sourced by Ant Financial. com.alipay.sofa sofa-ark-plugin-maven-plugin 2.2.12 sofa-ark false true ark-plugin runtime false true false false false true package com.alipay.sofa.ark.plugin.mojo.ArkPluginMojo java per-lookup once-per-session 0.1.0 false activator java.lang.String false true artifactId java.lang.String false false attach java.lang.Boolean false true whether install ark-plugin to local maven repository, if 'true', it will be installed when execute 'mvn install' or 'mvn deploy'; default set 'true'. classifier java.lang.String false true default ark plugin artifact classifier: empty exportClass java.lang.Boolean false true Export plugin project package by default description java.lang.String false true excludeArtifactIds java.util.LinkedHashSet false true list of artifact names to exclude (exact match). excludeGroupIds java.util.LinkedHashSet false true list of groupId names to exclude (exact match). excludes java.util.LinkedHashSet false true Colon separated groupId, artifactId [and classifier] to exclude (exact match) exported com.alipay.sofa.ark.plugin.mojo.ExportConfig false true groupId java.lang.String false false The configuration of ark plugin imported com.alipay.sofa.ark.plugin.mojo.ImportConfig false true outputDirectory java.io.File false true The location of the generated ark plugin pluginName java.lang.String false true priority java.lang.Integer false true shades java.util.LinkedHashSet false true Colon separated groupId, artifactId, classifier(optional), version. version java.lang.String false false workDirectory java.io.File false true The location of sofa-ark-maven-plugin temporary file project org.apache.maven.project.MavenProject true false ${sofa.ark.plugin.repository} ${sofa.ark.plugin.priority} org.codehaus.plexus.archiver.manager.ArchiverManager archiverManager org.apache.maven.project.MavenProjectHelper projectHelper com.alipay.sofa sofa-ark-spi jar 2.0.1-SNAPSHOT com.alipay.sofa sofa-ark-exception jar 2.0.1-SNAPSHOT com.alipay.sofa sofa-ark-common jar 2.0.1-SNAPSHOT com.alipay.sofa log-sofa-boot-starter jar 3.2.0 com.alipay.sofa.common sofa-common-tools jar 1.0.19 com.google.inject guice jar 4.0 javax.inject javax.inject jar 1 aopalliance aopalliance jar 1.0 com.google.guava guava jar 16.0.1 ch.qos.logback logback-classic jar 1.1.11 org.slf4j slf4j-api jar 1.7.32 ch.qos.logback logback-core jar 1.1.11 com.alipay.sofa sofa-ark-tools jar 2.0.1-SNAPSHOT org.ow2.asm asm jar 8.0 org.apache.maven maven-artifact jar 2.2.1 commons-io commons-io jar 2.5 org.apache.maven maven-core jar 3.8.1 org.apache.maven maven-settings jar 2.2.1 org.apache.maven.wagon wagon-file jar 1.0-beta-6 org.apache.maven maven-plugin-parameter-documenter jar 2.2.1 org.apache.maven.wagon wagon-http-lightweight jar 1.0-beta-6 org.apache.maven.wagon wagon-http-shared jar 1.0-beta-6 nekohtml xercesMinimal jar 1.9.6.2 nekohtml nekohtml jar 1.9.6.2 org.apache.maven.wagon wagon-http jar 1.0-beta-6 org.apache.maven.wagon wagon-webdav-jackrabbit jar 1.0-beta-6 org.apache.jackrabbit jackrabbit-webdav jar 1.5.0 org.apache.jackrabbit jackrabbit-jcr-commons jar 1.5.0 commons-httpclient commons-httpclient jar 3.0 commons-codec commons-codec jar 1.2 org.slf4j slf4j-nop jar 1.5.3 org.slf4j slf4j-jdk14 jar 1.5.6 org.slf4j jcl-over-slf4j jar 1.5.6 org.apache.maven.reporting maven-reporting-api jar 2.2.1 org.apache.maven.doxia doxia-sink-api jar 1.1 org.apache.maven.doxia doxia-logging-api jar 1.1 org.apache.maven maven-profile jar 2.2.1 org.apache.maven maven-model jar 2.2.1 org.apache.maven.wagon wagon-provider-api jar 1.0-beta-6 org.codehaus.plexus plexus-container-default jar 1.0-alpha-9-stable-1 org.apache.maven maven-repository-metadata jar 2.2.1 org.apache.maven maven-error-diagnostics jar 2.2.1 org.apache.maven maven-core jar 3.8.1 org.apache.maven maven-plugin-registry jar 2.2.1 commons-cli commons-cli jar 1.2 org.apache.maven maven-plugin-api jar 3.1.1 org.eclipse.sisu org.eclipse.sisu.plexus jar 0.0.0.M5 javax.enterprise cdi-api jar 1.0 javax.annotation jsr250-api jar 1.0 org.sonatype.sisu sisu-guice jar 3.1.0 org.eclipse.sisu org.eclipse.sisu.inject jar 0.0.0.M5 org.codehaus.plexus plexus-classworlds jar 2.4 org.apache.maven.wagon wagon-ssh-external jar 1.0-beta-6 org.apache.maven.wagon wagon-ssh-common jar 1.0-beta-6 org.apache.maven maven-plugin-descriptor jar 2.2.1 org.codehaus.plexus plexus-interactivity-api jar 1.0-alpha-4 org.apache.maven maven-artifact-manager jar 2.2.1 backport-util-concurrent backport-util-concurrent jar 3.1 org.apache.maven maven-monitor jar 2.2.1 org.apache.maven.wagon wagon-ssh jar 1.0-beta-6 com.jcraft jsch jar 0.1.38 classworlds classworlds jar 1.1 org.sonatype.plexus plexus-sec-dispatcher jar 1.3 org.sonatype.plexus plexus-cipher jar 1.4 org.apache.maven.plugin-tools maven-plugin-annotations jar 3.2 org.apache.maven maven-archiver jar 2.5 org.codehaus.plexus plexus-archiver jar 2.1 org.codehaus.plexus plexus-io jar 2.0.2 org.codehaus.plexus plexus-interpolation jar 1.15 org.apache.maven.shared maven-common-artifact-filters jar 1.4 org.codehaus.plexus plexus-component-annotations jar 1.5.5 org.codehaus.plexus plexus-utils jar 3.0 org.apache.commons commons-lang3 jar 3.3.1 ================================================ FILE: sofa-ark-parent/support/ark-plugin-maven-plugin/src/test/java/com/alipay/sofa/ark/plugin/mojo/ArkPluginMojoTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin.mojo; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DefaultArtifact; import org.apache.maven.artifact.handler.DefaultArtifactHandler; import org.apache.maven.model.Build; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.archiver.AbstractArchiver; import org.codehaus.plexus.archiver.manager.ArchiverManager; import org.codehaus.plexus.archiver.zip.ZipArchiver; import org.junit.Test; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class ArkPluginMojoTest { ArkPluginMojo arkPluginMojo = new ArkPluginMojo(); @Test public void testExecute() throws Exception { ArchiverManager archiverManager = mock(ArchiverManager.class); AtomicInteger finalResourcesCountInJar = new AtomicInteger(0); when(archiverManager.getArchiver("zip")).thenReturn(new ZipArchiver() { @Override public void cleanUp() throws IOException { try { Field field = AbstractArchiver.class.getDeclaredField("resources"); field.setAccessible(true); finalResourcesCountInJar.set(((List) field.get(this)).size()); } catch (Exception e) { throw new RuntimeException(e); } super.cleanUp(); } }); Field field = ArkPluginMojo.class.getDeclaredField("archiverManager"); field.setAccessible(true); field.set(arkPluginMojo, archiverManager); File outputDirectory = new File("./"); field = ArkPluginMojo.class.getDeclaredField("outputDirectory"); field.setAccessible(true); field.set(arkPluginMojo, outputDirectory); new File(outputDirectory, "xxx.ark.plugin").createNewFile(); new File(outputDirectory, "xxx.ark.plugin.bak").createNewFile(); LinkedHashSet excludes = new LinkedHashSet<>(); excludes.add("group1:artifact3"); excludes.add("group1:artifact2:17"); excludes.add("groupx:x"); arkPluginMojo.excludes = excludes; LinkedHashSet excludeGroupIds = new LinkedHashSet<>(); excludeGroupIds.add("groupy"); arkPluginMojo.excludeGroupIds = excludeGroupIds; LinkedHashSet excludeArtifactIds = new LinkedHashSet<>(); excludeArtifactIds.add("art1"); arkPluginMojo.excludeArtifactIds = excludeArtifactIds; Set artifacts = new HashSet<>(); DefaultArtifact defaultArtifact = new DefaultArtifact("groupx", "x", "version", "provided", "jar", "17", new DefaultArtifactHandler()); defaultArtifact.setFile(new File("src/test/resources/test-demo.jar")); artifacts.add(defaultArtifact); artifacts.add(new DefaultArtifact("group1", "artifact3", "version", "provided", "jar", null, new DefaultArtifactHandler())); artifacts.add(new DefaultArtifact("group1", "artifact2", "version", "provided", "jar", "17", new DefaultArtifactHandler())); defaultArtifact = new DefaultArtifact("groupyxx", "x", "version", "provided", "jar", "17", new DefaultArtifactHandler()); defaultArtifact.setFile(new File("src/test/resources/test-demo.jar")); artifacts.add(defaultArtifact); artifacts.add(new DefaultArtifact("groupy", "x", "version", "provided", "jar", "17", new DefaultArtifactHandler())); artifacts.add(new DefaultArtifact("groupyxx", "art1", "version", "provided", "jar", "17", new DefaultArtifactHandler())); DefaultArtifact shadeArtifact = new DefaultArtifact("shadeGroup", "shadeArtifact", "shadeVersion", "provided", "jar", "shadeClassifier", new DefaultArtifactHandler()); shadeArtifact.setFile(new File("src/test/resources/test-demo.jar")); artifacts.add(shadeArtifact); DefaultArtifact projectArtifact = new DefaultArtifact("a", "b", "c", "compile", "jar", null, new DefaultArtifactHandler()); projectArtifact.setFile(new File("src/test/resources/test-demo.jar")); Build build = new Build(); build.setOutputDirectory("./notexist"); MavenProject mavenProject = mock(MavenProject.class); when(mavenProject.getArtifacts()).thenReturn(artifacts); when(mavenProject.getArtifact()).thenReturn(projectArtifact); when(mavenProject.getBuild()).thenReturn(build); arkPluginMojo.setProject(mavenProject); arkPluginMojo.setShades(new LinkedHashSet<>(Collections .singleton("shadeGroup:shadeArtifact:shadeVersion:shadeClassifier"))); field = ArkPluginMojo.class.getDeclaredField("attach"); field.setAccessible(true); field.set(arkPluginMojo, true); arkPluginMojo.groupId = "a"; arkPluginMojo.artifactId = "b"; arkPluginMojo.version = "c"; arkPluginMojo.priority = 10; arkPluginMojo.pluginName = "xxx"; arkPluginMojo.description = "yyy"; arkPluginMojo.workDirectory = new File("./"); arkPluginMojo.exportPluginClass = true; arkPluginMojo.execute(); assertEquals(4, finalResourcesCountInJar.get()); } @Test public void testExportConfig() { Properties properties = new Properties(); ExportConfig exportConfig = new ExportConfig(); LinkedHashSet classes = new LinkedHashSet(); classes.add("a"); classes.add("b"); exportConfig.setClasses(classes); exportConfig.store(properties); assertEquals("a,b", properties.getProperty("export-classes")); assertEquals("", properties.getProperty("export-mode")); assertEquals("", properties.getProperty("export-resources")); assertEquals("", properties.getProperty("export-packages")); } @Test public void testImportConfig() { Properties properties = new Properties(); ImportConfig importConfig = new ImportConfig(); LinkedHashSet resources = new LinkedHashSet(); resources.add("c"); resources.add("d"); importConfig.setResources(resources); importConfig.store(properties); assertEquals("", properties.getProperty("import-classes")); assertEquals("c,d", properties.getProperty("import-resources")); assertEquals("", properties.getProperty("import-packages")); } } ================================================ FILE: sofa-ark-parent/support/ark-plugin-maven-plugin/src/test/java/com/alipay/sofa/ark/plugin/mojo/LinkedAttributesTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin.mojo; import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import static java.util.jar.Attributes.Name.*; import static org.apache.commons.lang3.StringUtils.repeat; import static org.junit.Assert.assertEquals; public class LinkedAttributesTest { private LinkedAttributes linkedAttributes = new LinkedAttributes(); @Test public void testLinkedAttributesAllMethods() throws Exception { assertEquals(0, linkedAttributes.entrySet().size()); linkedAttributes.put(EXTENSION_NAME, "b"); assertEquals(1, linkedAttributes.entrySet().size()); linkedAttributes.remove(EXTENSION_NAME); assertEquals(0, linkedAttributes.entrySet().size()); linkedAttributes.put(MANIFEST_VERSION, "1.0.0"); linkedAttributes.put(CONTENT_TYPE, "d"); linkedAttributes.put(IMPLEMENTATION_TITLE, repeat("f", 150)); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); linkedAttributes.writeMain(new DataOutputStream(outputStream)); outputStream.flush(); assertEquals("Manifest-Version: 1.0.0\r\n" + "Content-Type: d\r\n" + "Implementation-Title: " + repeat("f", 48) + "\r\n " + repeat("f", 69) + "\r\n " + repeat("f", 33) + "\r\n\r\n", outputStream.toString("UTF-8")); } @Test public void testLinkedManifestWriteEmpty() throws Exception { LinkedManifest linkedManifest = new LinkedManifest(); linkedManifest.getMainAttributes().putValue("a", "b"); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); linkedManifest.write(outputStream); outputStream.flush(); assertEquals("\r\n", outputStream.toString("UTF-8")); } } ================================================ FILE: sofa-ark-parent/support/ark-plugin-maven-plugin/src/test/java/com/alipay/sofa/ark/plugin/mojo/LinkedPropertiesTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin.mojo; import org.junit.Test; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.assertEquals; public class LinkedPropertiesTest { private LinkedProperties linkedProperties = new LinkedProperties(); @Test public void testLinkedPropertiesAllMethods() { assertEquals(false, linkedProperties.keys().hasMoreElements()); linkedProperties.put("a", "b"); assertEquals(1, linkedProperties.entrySet().size()); linkedProperties.remove("a"); assertEquals(0, linkedProperties.entrySet().size()); Map properties = new HashMap<>(); properties.put("c", "d"); properties.put("e", "f"); linkedProperties.putAll(properties); assertEquals(2, linkedProperties.entrySet().size()); } } ================================================ FILE: sofa-ark-parent/support/ark-plugin-maven-plugin/src/test/java/com/alipay/sofa/ark/plugin/mojo/test/ArkPluginMojoTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.plugin.mojo.test; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.plugin.mojo.ArkPluginMojo; import org.apache.maven.artifact.Artifact; import org.apache.maven.project.MavenProject; import org.junit.Test; import org.mockito.Mockito; import java.io.File; import java.io.FileInputStream; import java.net.URL; import java.net.URLClassLoader; import java.util.Collections; import java.util.LinkedHashSet; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; /** * @author qilong.zql * @since 0.4.0 */ public class ArkPluginMojoTest { @Test public void testArkPluginMojo() throws Exception { Artifact artifact = Mockito.mock(Artifact.class); MavenProject project = Mockito.mock(MavenProject.class); when(artifact.getGroupId()).thenReturn("invalid"); when(artifact.getArtifactId()).thenReturn("invalid"); when(project.getArtifact()).thenReturn(artifact); when(artifact.getFile()).thenReturn( new File(Test.class.getProtectionDomain().getCodeSource().getLocation().getPath())); ArkPluginMojo arkPluginMojo = new ArkPluginMojo(); arkPluginMojo.setProject(project); arkPluginMojo.setShades(new LinkedHashSet<>(Collections .singleton("com.alipay.sofa:test-demo:1.0.0"))); final URL url = this.getClass().getClassLoader().getResource("test-demo.jar"); String path = url.getPath() + ".shaded"; String shadedUrl = url.toExternalForm() + ".shaded"; String copyPath = url.getPath() + ".copy"; File copyFileForTest = new File(copyPath); FileInputStream demoJar = new FileInputStream(url.getPath()); FileUtils.copyInputStreamToFile(demoJar, new File(copyPath)); demoJar.close(); arkPluginMojo.shadeJarIntoArkPlugin(new File(path), copyFileForTest, Collections.singleton(artifact)); assertTrue(copyFileForTest.delete()); URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { new URL(shadedUrl) }, null); assertNotNull(urlClassLoader.loadClass("com.alipay.sofa.support.test.SampleService")); assertNotNull(urlClassLoader.loadClass("org.junit.Test")); } @Test public void testShadeJar() { ArkPluginMojo arkPluginMojo = new ArkPluginMojo(); arkPluginMojo.setShades(new LinkedHashSet<>(Collections .singleton("com.alipay.sofa:test-demo:1.0.0"))); MavenProject projectOne = Mockito.mock(MavenProject.class); MavenProject projectTwo = Mockito.mock(MavenProject.class); Artifact artifact = Mockito.mock(Artifact.class); when(projectOne.getGroupId()).thenReturn("com.alipay.sofa"); when(projectOne.getArtifactId()).thenReturn("test-demo"); when(projectTwo.getGroupId()).thenReturn("com.alipay.sofa"); when(projectTwo.getArtifactId()).thenReturn(""); when(artifact.getGroupId()).thenReturn("com.alipay.sofa"); when(artifact.getArtifactId()).thenReturn("test-demo"); when(artifact.getVersion()).thenReturn("1.0.0"); arkPluginMojo.setProject(projectOne); try { arkPluginMojo.isShadeJar(artifact); } catch (Exception ex) { assertTrue(ex.getMessage().equals("Can't shade jar-self.")); } arkPluginMojo.setProject(projectTwo); assertTrue(arkPluginMojo.isShadeJar(artifact)); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-common-springboot/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-common-springboot` **Package**: `com.alipay.sofa.ark.springboot` This module provides common Spring Boot integration code shared across different Spring Boot versions. ## Purpose - Conditional annotations for Ark-enabled features - Spring Boot version detection ## Key Classes ### `condition.ConditionalOnArkEnabled` Conditional annotation to enable features only when Ark is active: ```java @ConditionalOnArkEnabled @Bean public MyBean myBean() { ... } ``` ### `condition.OnArkEnabled` Condition implementation that checks if Ark container is running. ### `condition.ConditionalOnSpringBootVersion` Conditional annotation based on Spring Boot version: ```java @ConditionalOnSpringBootVersion("2.x") @Bean public MyBean myBean() { ... } ``` ### `condition.OnSpringBootVersion` Condition implementation that matches specific Spring Boot versions. ## Dependencies - `sofa-ark-spi` - Service interfaces - Spring Boot (provided scope) ## Used By - `ark-compatible-springboot1` - Spring Boot 1.x compatibility - `ark-compatible-springboot2` - Spring Boot 2.x compatibility - `ark-springboot-starter` - Main starter ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-common-springboot/pom.xml ================================================ sofa-ark-support com.alipay.sofa ${sofa.ark.version} ../../pom.xml 4.0.0 sofa-ark-common-springboot ${project.groupId}:${project.artifactId} 1.5.22.RELEASE org.springframework.boot spring-boot ${spring.boot.version} provided org.springframework.boot spring-boot-autoconfigure ${spring.boot.version} provided com.alipay.sofa sofa-ark-api ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-common-springboot/src/main/java/com/alipay/sofa/ark/springboot/condition/ConditionalOnArkEnabled.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.condition; import org.springframework.context.annotation.Conditional; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author qilong.zql 19/2/27 * @since 0.6.0 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnArkEnabled.class) public @interface ConditionalOnArkEnabled { } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-common-springboot/src/main/java/com/alipay/sofa/ark/springboot/condition/ConditionalOnSpringBootVersion.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.condition; import org.springframework.context.annotation.Conditional; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author qilong.zql * @since 0.6.0 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnSpringBootVersion.class) public @interface ConditionalOnSpringBootVersion { /** * The required version of spring boot * @return the required spring boot version */ Version version() default Version.ANY; /** * Available application types. */ enum Version { /** * Spring Boot 1.x */ OneX, /** * Spring Boot 2.x */ TwoX, /** * Any Spring Boot Version */ ANY } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-common-springboot/src/main/java/com/alipay/sofa/ark/springboot/condition/OnArkEnabled.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.condition; import com.alipay.sofa.ark.api.ArkConfigs; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.type.AnnotatedTypeMetadata; /** * @author qilong.zql * @since 0.6.0 */ @Order(Ordered.HIGHEST_PRECEDENCE + 100) public class OnArkEnabled extends SpringBootCondition { private static final String ARK_TEST_CLASSLOADER_NAME = "com.alipay.sofa.ark.container.test.TestClassLoader"; private static final String ARK_BIZ_CLASSLOADER_NAME = "com.alipay.sofa.ark.container.service.classloader.BizClassLoader"; private static final String ARK_PLUGIN_CLASSLOADER_NAME = "com.alipay.sofa.ark.container.service.classloader.PluginClassLoader"; @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { String currentClassLoader = this.getClass().getClassLoader().getClass().getName(); if (ARK_TEST_CLASSLOADER_NAME.equals(currentClassLoader) || ARK_BIZ_CLASSLOADER_NAME.equals(currentClassLoader) || ARK_PLUGIN_CLASSLOADER_NAME.equals(currentClassLoader)) { return new ConditionOutcome(true, "SOFAArk has started."); } else if (ArkConfigs.isEmbedEnable()) { return new ConditionOutcome(true, "Embed SOFAArk has started."); } else { return new ConditionOutcome(false, "SOFAArk has not started."); } } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-common-springboot/src/main/java/com/alipay/sofa/ark/springboot/condition/OnSpringBootVersion.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.condition; import org.springframework.boot.SpringBootVersion; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.type.AnnotatedTypeMetadata; import java.util.Map; /** * @author qilong.zql * @since 0.6.0 */ @Order(Ordered.HIGHEST_PRECEDENCE + 100) public class OnSpringBootVersion extends SpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { Map springBootVersion = metadata .getAnnotationAttributes(ConditionalOnSpringBootVersion.class.getCanonicalName()); if (springBootVersion == null || springBootVersion.get("version") == null) { return new ConditionOutcome(false, "No specified spring boot version."); } ConditionalOnSpringBootVersion.Version version = (ConditionalOnSpringBootVersion.Version) springBootVersion .get("version"); if (ConditionalOnSpringBootVersion.Version.ANY.equals(version)) { return new ConditionOutcome(true, "Conditional on Any Spring Boot."); } else if (ConditionalOnSpringBootVersion.Version.OneX.equals(version)) { String bootVersion = SpringBootVersion.getVersion(); if (null != bootVersion && bootVersion.startsWith("1")) { return new ConditionOutcome(true, "Conditional on OneX Spring Boot."); } else { return new ConditionOutcome(false, "Conditional on OneX Spring Boot."); } } else if (ConditionalOnSpringBootVersion.Version.TwoX.equals(version)) { String bootVersion = SpringBootVersion.getVersion(); if (null != bootVersion && bootVersion.startsWith("2")) { return new ConditionOutcome(true, "Conditional on TwoX Spring Boot."); } else { return new ConditionOutcome(false, "Conditional on TwoX Spring Boot."); } } throw new IllegalStateException("Error Spring Boot Version."); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot1/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-compatible-springboot1` **Package**: `com.alipay.sofa.ark.springboot` This module provides Spring Boot 1.x compatibility for SOFAArk. ## Purpose - Enable Ark features in Spring Boot 1.x applications - Version-specific adapter implementations ## Notes - Spring Boot 1.x is end-of-life - This module provides backward compatibility for legacy applications - For new projects, use Spring Boot 2.x or later ## Dependencies - `sofa-ark-common-springboot` - Common integration code - Spring Boot 1.x (provided scope) ## Used By - `ark-springboot-starter` - Conditionally loaded for Spring Boot 1.x apps ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot1/pom.xml ================================================ sofa-ark-support com.alipay.sofa ${sofa.ark.version} ../../pom.xml 4.0.0 sofa-ark-compatible-springboot1 ${project.groupId}:${project.artifactId} 1.5.22.RELEASE org.springframework.boot spring-boot-actuator ${spring.boot.version} provided org.springframework.boot spring-boot-starter-web ${spring.boot.version} provided com.alipay.sofa sofa-ark-api com.alipay.sofa sofa-ark-common-springboot junit junit test com.alipay.sofa sofa-ark-support-starter test com.alipay.sofa sofa-ark-all ${sofa.ark.version.old} test com.alipay.sofa web-ark-plugin test ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot1/src/main/java/com/alipay/sofa/ark/springboot1/CompatibleSpringBoot1AutoConfiguration.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot1; import com.alipay.sofa.ark.springboot.condition.ConditionalOnArkEnabled; import com.alipay.sofa.ark.springboot.condition.ConditionalOnSpringBootVersion; import com.alipay.sofa.ark.springboot1.endpoint.IntrospectBizEndpoint; import com.alipay.sofa.ark.springboot1.endpoint.IntrospectBizEndpointMvcAdapter; import org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author qilong.zql * @since 0.6.0 */ @Configuration @ConditionalOnSpringBootVersion(version = ConditionalOnSpringBootVersion.Version.OneX) @ConditionalOnArkEnabled public class CompatibleSpringBoot1AutoConfiguration { @ConditionalOnClass(name = "org.springframework.boot.actuate.endpoint.Endpoint") public static class ConditionIntrospectEndpointConfiguration { @Bean @ConditionalOnEnabledEndpoint("bizState") public IntrospectBizEndpoint introspectBizEndpoint() { return new IntrospectBizEndpoint(); } } @Bean @ConditionalOnBean(IntrospectBizEndpoint.class) @ConditionalOnWebApplication public IntrospectBizEndpointMvcAdapter introspectBizEndpointMvcAdapter(IntrospectBizEndpoint introspectBizEndpoint) { return new IntrospectBizEndpointMvcAdapter(introspectBizEndpoint); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot1/src/main/java/com/alipay/sofa/ark/springboot1/endpoint/IntrospectBizEndpoint.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot1.endpoint; import com.alipay.sofa.ark.api.ArkClient; import org.springframework.boot.actuate.endpoint.AbstractEndpoint; /** * @author qilong.zql * @since 0.6.0 */ public class IntrospectBizEndpoint extends AbstractEndpoint { public IntrospectBizEndpoint() { super("bizState", false, true); } @Override public Object invoke() { return ArkClient.checkBiz(); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot1/src/main/java/com/alipay/sofa/ark/springboot1/endpoint/IntrospectBizEndpointMvcAdapter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot1.endpoint; import org.springframework.boot.actuate.endpoint.mvc.AbstractEndpointMvcAdapter; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; /** * @author qilong.zql * @since 0.6.0 */ public class IntrospectBizEndpointMvcAdapter extends AbstractEndpointMvcAdapter { public IntrospectBizEndpointMvcAdapter(IntrospectBizEndpoint delegate) { super(delegate); setPath("bizState"); } @Override @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody protected Object invoke() { return super.invoke(); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot1/src/main/java/com/alipay/sofa/ark/springboot1/web/ArkAutoConfiguration.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot1.web; import com.alipay.sofa.ark.springboot.condition.ConditionalOnArkEnabled; import org.apache.catalina.startup.Tomcat; import org.apache.coyote.UpgradeProtocol; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Servlet; /** * @author qixiaobo * @since 2.2.4 */ @Configuration @ConditionalOnArkEnabled @ConditionalOnClass(EmbeddedServletContainerAutoConfiguration.class) @AutoConfigureBefore(EmbeddedServletContainerAutoConfiguration.class) public class ArkAutoConfiguration { @Configuration @ConditionalOnClass(value = { Servlet.class, Tomcat.class, UpgradeProtocol.class, ArkTomcatEmbeddedServletContainerFactory.class }, name = { "com.alipay.sofa.ark.web.embed.tomcat.ArkTomcatEmbeddedWebappClassLoader", "org.springframework.boot.context.embedded.EmbeddedServletContainerFactory" }) @ConditionalOnMissingBean(value = { EmbeddedServletContainerFactory.class }, search = SearchStrategy.CURRENT) public static class EmbeddedArkTomcat { @Bean @ConditionalOnMissingBean(ArkTomcatEmbeddedServletContainerFactory.class) public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() { return new ArkTomcatEmbeddedServletContainerFactory(); } } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot1/src/main/java/com/alipay/sofa/ark/springboot1/web/ArkTomcatEmbeddedServletContainer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot1.web; import org.apache.catalina.*; import org.apache.catalina.connector.Connector; import org.apache.catalina.startup.Tomcat; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.naming.ContextBindings; import org.springframework.boot.context.embedded.EmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerException; import org.springframework.boot.context.embedded.tomcat.ConnectorStartFailedException; import org.springframework.util.Assert; import javax.naming.NamingException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; /** * @author qixiaobo * @since 2.2.4 */ public class ArkTomcatEmbeddedServletContainer implements EmbeddedServletContainer { private static final Log logger = LogFactory .getLog(ArkTomcatEmbeddedServletContainer.class); private static final AtomicInteger containerCounter = new AtomicInteger(-1); private final Object monitor = new Object(); private final Map serviceConnectors = new HashMap<>(); private final Tomcat tomcat; private final boolean autoStart; private volatile boolean started; private Thread awaitThread; private Tomcat arkEmbedTomcat; /** * Create a new {@link ArkTomcatEmbeddedServletContainer} instance. * @param tomcat the underlying Tomcat server */ public ArkTomcatEmbeddedServletContainer(Tomcat tomcat) { this(tomcat, true); } /** * Create a new {@link ArkTomcatEmbeddedServletContainer} instance. * @param tomcat the underlying Tomcat server * @param autoStart if the server should be started */ public ArkTomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; initialize(); } public ArkTomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart, Tomcat arkEmbedTomcat) { this(tomcat, autoStart); this.arkEmbedTomcat = arkEmbedTomcat; } private void initialize() throws EmbeddedServletContainerException { logger.info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { addInstanceIdToEngineName(); Context context = findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { // Remove service connectors so that protocol binding doesn't // happen when the service is started. removeServiceConnectors(); } }); // Start the server to trigger initialization listeners this.tomcat.start(); // We can re-throw failure exception directly in the main thread rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), Thread.currentThread().getContextClassLoader()); } catch (NamingException ex) { // Naming is not enabled. Continue } // Unlike Jetty, all Tomcat threads are daemon threads. We create a // blocking non-daemon to stop immediate shutdown startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); throw new EmbeddedServletContainerException("Unable to start embedded Tomcat", ex); } } } private Context findContext() { for (Container child : this.tomcat.getHost().findChildren()) { if (child instanceof Context) { if (child.getParentClassLoader().equals( Thread.currentThread().getContextClassLoader())) { return (Context) child; } } } throw new IllegalStateException("The host does not contain a Context"); } private void addInstanceIdToEngineName() { int instanceId = containerCounter.incrementAndGet(); if (instanceId > 0) { Engine engine = this.tomcat.getEngine(); engine.setName(engine.getName() + "-" + instanceId); } } private void removeServiceConnectors() { for (Service service : this.tomcat.getServer().findServices()) { Connector[] connectors = service.findConnectors().clone(); this.serviceConnectors.put(service, connectors); for (Connector connector : connectors) { service.removeConnector(connector); } } } private void rethrowDeferredStartupExceptions() throws Exception { Container[] children = this.tomcat.getHost().findChildren(); for (Container container : children) { // just to check current biz status if (container.getParentClassLoader() == Thread.currentThread().getContextClassLoader()) { if (!LifecycleState.STARTED.equals(container.getState())) { throw new IllegalStateException(container + " failed to start"); } } } } private void startDaemonAwaitThread() { awaitThread = new Thread("container-" + (containerCounter.get())) { @Override public void run() { getTomcat().getServer().await(); } }; awaitThread.setContextClassLoader(Thread.currentThread().getContextClassLoader()); awaitThread.setDaemon(false); awaitThread.start(); } @Override public void start() throws EmbeddedServletContainerException { synchronized (this.monitor) { if (this.started) { return; } try { addPreviouslyRemovedConnectors(); this.tomcat.getConnector(); checkThatConnectorsHaveStarted(); this.started = true; logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '" + getContextPath() + "'"); } catch (ConnectorStartFailedException ex) { stopSilently(); throw ex; } catch (Exception ex) { throw new EmbeddedServletContainerException( "Unable to start embedded Tomcat server", ex); } finally { Context context = findContext(); ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass() .getClassLoader()); } } } private void checkThatConnectorsHaveStarted() { checkConnectorHasStarted(this.tomcat.getConnector()); for (Connector connector : this.tomcat.getService().findConnectors()) { checkConnectorHasStarted(connector); } } private void checkConnectorHasStarted(Connector connector) { if (LifecycleState.FAILED.equals(connector.getState())) { throw new ConnectorStartFailedException(connector.getPort()); } } public void stopSilently() { stopContext(); try { stopTomcatIfNecessary(); } catch (LifecycleException ex) { // Ignore } } private void stopContext() { Context context = findContext(); getTomcat().getHost().removeChild(context); } private void stopTomcatIfNecessary() throws LifecycleException { if (tomcat != arkEmbedTomcat) { tomcat.destroy(); } awaitThread.stop(); } private void addPreviouslyRemovedConnectors() { Service[] services = this.tomcat.getServer().findServices(); for (Service service : services) { Connector[] connectors = this.serviceConnectors.get(service); if (connectors != null) { for (Connector connector : connectors) { service.addConnector(connector); if (!this.autoStart) { stopProtocolHandler(connector); } } this.serviceConnectors.remove(service); } } } private void stopProtocolHandler(Connector connector) { try { connector.getProtocolHandler().stop(); } catch (Exception ex) { logger.error("Cannot pause connector: ", ex); } } Map getServiceConnectors() { return this.serviceConnectors; } @Override public void stop() throws EmbeddedServletContainerException { synchronized (this.monitor) { boolean wasStarted = this.started; try { this.started = false; try { stopContext(); stopTomcatIfNecessary(); } catch (Throwable ex) { // swallow and continue } } catch (Exception ex) { throw new EmbeddedServletContainerException("Unable to stop embedded Tomcat", ex); } finally { if (wasStarted) { containerCounter.decrementAndGet(); } } } } private String getPortsDescription(boolean localPort) { StringBuilder ports = new StringBuilder(); for (Connector connector : this.tomcat.getService().findConnectors()) { if (ports.length() != 0) { ports.append(' '); } int port = localPort ? connector.getLocalPort() : connector.getPort(); ports.append(port).append(" (").append(connector.getScheme()).append(')'); } return ports.toString(); } @Override public int getPort() { Connector connector = this.tomcat.getConnector(); if (connector != null) { return connector.getLocalPort(); } return 0; } private String getContextPath() { return findContext().getPath(); } /** * Returns access to the underlying Tomcat server. * @return the Tomcat server */ public Tomcat getTomcat() { return this.tomcat; } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot1/src/main/java/com/alipay/sofa/ark/springboot1/web/ArkTomcatEmbeddedServletContainerFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot1.web; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.service.ArkInject; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.web.EmbeddedServerService; import org.apache.catalina.*; import org.apache.catalina.connector.Connector; import org.apache.catalina.core.StandardContext; import org.apache.catalina.loader.WebappLoader; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.webresources.StandardRoot; import org.apache.tomcat.util.scan.StandardJarScanFilter; import org.springframework.boot.context.embedded.EmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerException; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import javax.servlet.ServletContainerInitializer; import java.io.File; import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Locale; import java.util.Map; import static com.alipay.sofa.ark.spi.constant.Constants.ROOT_WEB_CONTEXT_PATH; /** * @author qixiaobo * @since 2.2.4 */ public class ArkTomcatEmbeddedServletContainerFactory extends TomcatEmbeddedServletContainerFactory { private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; @ArkInject private EmbeddedServerService embeddedServerService; @ArkInject private BizManagerService bizManagerService; private File baseDirectory; private String protocol = DEFAULT_PROTOCOL; private int backgroundProcessorDelay; @Override public EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers) { if (embeddedServerService == null) { Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.getAdditionalTomcatConnectors()) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); return getEmbeddedServletContainer(tomcat); } else if (embeddedServerService.getEmbedServer(getPort()) == null) { embeddedServerService.putEmbedServer(getPort(), initEmbedTomcat()); } Tomcat embedTomcat = embeddedServerService.getEmbedServer(getPort()); prepareContext(embedTomcat.getHost(), initializers); return getEmbeddedServletContainer(embedTomcat); } @Override public String getContextPath() { String contextPath = super.getContextPath(); if (bizManagerService == null) { return contextPath; } Biz biz = bizManagerService.getBizByClassLoader(Thread.currentThread() .getContextClassLoader()); if (!StringUtils.isEmpty(contextPath)) { return contextPath; } else if (biz != null) { if (StringUtils.isEmpty(biz.getWebContextPath())) { return ROOT_WEB_CONTEXT_PATH; } return biz.getWebContextPath(); } else { return ROOT_WEB_CONTEXT_PATH; } } private Tomcat initEmbedTomcat() { Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : getAdditionalTomcatConnectors()) { tomcat.getService().addConnector(additionalConnector); } return tomcat; } @Override public void setBaseDirectory(File baseDirectory) { this.baseDirectory = baseDirectory; } /** * The Tomcat protocol to use when create the {@link Connector}. * * @param protocol the protocol * @see Connector#Connector(String) */ @Override public void setProtocol(String protocol) { AssertUtils.isFalse(StringUtils.isEmpty(protocol), "Protocol must not be empty"); this.protocol = protocol; } @Override public void setBackgroundProcessorDelay(int delay) { this.backgroundProcessorDelay = delay; } private void configureEngine(Engine engine) { engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay); for (Valve valve : getEngineValves()) { engine.getPipeline().addValve(valve); } } @Override protected void postProcessContext(Context context) { ((WebappLoader) context.getLoader()) .setLoaderClass("com.alipay.sofa.ark.web.embed.tomcat.ArkTomcatEmbeddedWebappClassLoader"); } @Override protected void prepareContext(Host host, ServletContextInitializer[] initializers) { if (host.getState() == LifecycleState.NEW) { super.prepareContext(host, initializers); } else { File documentRoot = getValidDocumentRoot(); StandardContext context = new StandardContext(); if (documentRoot != null) { context.setResources(new StandardRoot(context)); } context.setName(getContextPath()); context.setDisplayName(getDisplayName()); context.setPath(getContextPath()); File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase"); context.setDocBase(docBase.getAbsolutePath()); context.addLifecycleListener(new Tomcat.FixContextListener()); context.setParentClassLoader(Thread.currentThread().getContextClassLoader()); resetDefaultLocaleMapping(context); addLocaleMappings(context); context.setUseRelativeRedirects(false); configureTldSkipPatterns(context); WebappLoader loader = new WebappLoader(context.getParentClassLoader()); loader .setLoaderClass("com.alipay.sofa.ark.web.embed.tomcat.ArkTomcatEmbeddedWebappClassLoader"); loader.setDelegate(true); context.setLoader(loader); if (isRegisterDefaultServlet()) { addDefaultServlet(context); } if (shouldRegisterJspServlet()) { addJspServlet(context); addJasperInitializer(context); } context.addLifecycleListener(new StaticResourceConfigurer(context)); ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); context.setParent(host); configureContext(context, initializersToUse); host.addChild(context); } } /** * Override Tomcat's default locale mappings to align with other servers. See * {@code org.apache.catalina.util.CharsetMapperDefault.properties}. * * @param context the context to reset */ private void resetDefaultLocaleMapping(StandardContext context) { context.addLocaleEncodingMappingParameter(Locale.ENGLISH.toString(), DEFAULT_CHARSET.displayName()); context.addLocaleEncodingMappingParameter(Locale.FRENCH.toString(), DEFAULT_CHARSET.displayName()); } private void addLocaleMappings(StandardContext context) { for (Map.Entry entry : getLocaleCharsetMappings().entrySet()) { context.addLocaleEncodingMappingParameter(entry.getKey().toString(), entry.getValue() .toString()); } } private void configureTldSkipPatterns(StandardContext context) { StandardJarScanFilter filter = new StandardJarScanFilter(); filter.setTldSkip(StringUtils.collectionToCommaDelimitedString(getTldSkipPatterns())); context.getJarScanner().setJarScanFilter(filter); } private void addDefaultServlet(Context context) { Wrapper defaultServlet = context.createWrapper(); defaultServlet.setName("default"); defaultServlet.setServletClass("org.apache.catalina.servlets.DefaultServlet"); defaultServlet.addInitParameter("debug", "0"); defaultServlet.addInitParameter("listings", "false"); defaultServlet.setLoadOnStartup(1); // Otherwise the default location of a Spring DispatcherServlet cannot be set defaultServlet.setOverridable(true); context.addChild(defaultServlet); context.addServletMappingDecoded("/", "default"); } private void addJspServlet(Context context) { Wrapper jspServlet = context.createWrapper(); jspServlet.setName("jsp"); jspServlet.setServletClass(getJspServlet().getClassName()); jspServlet.addInitParameter("fork", "false"); for (Map.Entry initParameter : getJspServlet().getInitParameters() .entrySet()) { jspServlet.addInitParameter(initParameter.getKey(), initParameter.getValue()); } jspServlet.setLoadOnStartup(3); context.addChild(jspServlet); addServletMapping(context, "*.jsp", "jsp"); addServletMapping(context, "*.jspx", "jsp"); } @SuppressWarnings("deprecation") private void addServletMapping(Context context, String pattern, String name) { context.addServletMapping(pattern, name); } private void addJasperInitializer(StandardContext context) { try { ServletContainerInitializer initializer = (ServletContainerInitializer) ClassUtils .forName("org.apache.jasper.servlet.JasperInitializer", null).newInstance(); context.addServletContainerInitializer(initializer, null); } catch (Exception ex) { // Probably not Tomcat 8 } } final class StaticResourceConfigurer implements LifecycleListener { private final Context context; private StaticResourceConfigurer(Context context) { this.context = context; } @Override public void lifecycleEvent(LifecycleEvent event) { if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { addResourceJars(getUrlsOfJarsWithMetaInfResources()); } } private void addResourceJars(List resourceJarUrls) { for (URL url : resourceJarUrls) { String path = url.getPath(); if (path.endsWith(".jar") || path.endsWith(".jar!/")) { String jar = url.toString(); if (!jar.startsWith("jar:")) { // A jar file in the file system. Convert to Jar URL. jar = "jar:" + jar + "!/"; } addResourceSet(jar); } else { addResourceSet(url.toString()); } } } private void addResourceSet(String resource) { try { if (isInsideNestedJar(resource)) { // It's a nested jar but we now don't want the suffix because Tomcat // is going to try and locate it as a root URL (not the resource // inside it) resource = resource.substring(0, resource.length() - 2); } URL url = new URL(resource); String path = "/META-INF/resources"; this.context.getResources().createWebResourceSet( WebResourceRoot.ResourceSetType.RESOURCE_JAR, "/", url, path); } catch (Exception ex) { // Ignore (probably not a directory) } } private boolean isInsideNestedJar(String dir) { return dir.indexOf("!/") < dir.lastIndexOf("!/"); } } @Override protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) { throw new EmbeddedServletContainerException( "not support to invoke getTomcatEmbeddedServletContainer", null); } protected EmbeddedServletContainer getEmbeddedServletContainer(Tomcat tomcat) { return new ArkTomcatEmbeddedServletContainer(tomcat, getPort() >= 0, tomcat); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot1/src/main/java/com/alipay/sofa/ark/springboot1/web/SwitchClassLoaderFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot1.web; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; public class SwitchClassLoaderFilter implements Filter { /** * using user logger not ArkLogger, to print this log into user log dir */ private Logger logger = LoggerFactory.getLogger(SwitchClassLoaderFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { ClassLoader oldClassloader = Thread.currentThread().getContextClassLoader(); try { if ("com.alipay.sofa.ark.web.embed.tomcat.ArkTomcatEmbeddedWebappClassLoader" .equals(oldClassloader.getClass().getName())) { ClassLoader bizClassLoader = oldClassloader.getParent(); if (bizClassLoader != null) { logger.debug("switch web classLoader from {} to {}", oldClassloader, bizClassLoader); Thread.currentThread().setContextClassLoader(bizClassLoader); } } filterChain.doFilter(servletRequest, servletResponse); } finally { Thread.currentThread().setContextClassLoader(oldClassloader); } } @Override public void destroy() { } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot1/src/main/resources/META-INF/spring.factories ================================================ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.alipay.sofa.ark.springboot1.CompatibleSpringBoot1AutoConfiguration,\ com.alipay.sofa.ark.springboot1.web.ArkAutoConfiguration ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot1/src/test/java/com/alipay/sofa/ark/springboot1/web/ArkTomcatEmbeddedServletContainerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot1.web; import com.alipay.sofa.ark.web.embed.tomcat.EmbeddedServerServiceImpl; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Field; /** * @author qixiaobo * @since 0.6.0 */ public class ArkTomcatEmbeddedServletContainerTest { private ArkTomcatEmbeddedServletContainerFactory arkTomcatEmbeddedServletContainerFactory; private ArkTomcatEmbeddedServletContainer arkTomcatEmbeddedServletContainer; @Before public void setUp() throws Exception { arkTomcatEmbeddedServletContainerFactory = new ArkTomcatEmbeddedServletContainerFactory(); Field field = ArkTomcatEmbeddedServletContainerFactory.class .getDeclaredField("embeddedServerService"); field.setAccessible(true); field.set(arkTomcatEmbeddedServletContainerFactory, new EmbeddedServerServiceImpl()); arkTomcatEmbeddedServletContainer = (ArkTomcatEmbeddedServletContainer) arkTomcatEmbeddedServletContainerFactory .getEmbeddedServletContainer(); } @After public void tearDown() { } @Test public void testGetWebServerWithEmbeddedServerServiceNull() { // NOTE: tomcat can not be stopped and restarted due to a Spring context destroy problem. // Spring community will fix this issue in the future, so catch all exception now. try { arkTomcatEmbeddedServletContainer.stopSilently(); } catch (Exception e) { } try { arkTomcatEmbeddedServletContainer.stop(); } catch (Exception e) { } } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot1/src/test/java/com/alipay/sofa/ark/springboot1/web/ArkTomcatServletWebServerFactoryTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot1.web; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.service.biz.BizManagerServiceImpl; import org.apache.catalina.Context; import org.apache.catalina.LifecycleException; import org.apache.catalina.core.StandardContext; import org.apache.catalina.core.StandardHost; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.boot.web.servlet.ServletContextInitializer; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.List; import static com.alipay.sofa.ark.spi.constant.Constants.ROOT_WEB_CONTEXT_PATH; import static com.alipay.sofa.ark.spi.model.BizState.RESOLVED; import static java.lang.Thread.currentThread; import static org.junit.Assert.assertEquals; /** * @author qixiaobo * @since 0.6.0 */ public class ArkTomcatServletWebServerFactoryTest { private ArkTomcatEmbeddedServletContainerFactory arkTomcatEmbeddedServletContainerFactory = new ArkTomcatEmbeddedServletContainerFactory(); private ClassLoader currentThreadContextClassLoader; @Before public void setUp() { currentThreadContextClassLoader = currentThread().getContextClassLoader(); } @After public void tearDown() { currentThread().setContextClassLoader(currentThreadContextClassLoader); } @Test public void testGetWebServerWithEmbeddedServerServiceNull() { assertEquals(ArkTomcatEmbeddedServletContainer.class, arkTomcatEmbeddedServletContainerFactory.getEmbeddedServletContainer().getClass()); } @Test public void testGetContextPath() throws Exception { assertEquals("", arkTomcatEmbeddedServletContainerFactory.getContextPath()); BizManagerServiceImpl bizManagerService = new BizManagerServiceImpl(); Field field = ArkTomcatEmbeddedServletContainerFactory.class .getDeclaredField("bizManagerService"); field.setAccessible(true); field.set(arkTomcatEmbeddedServletContainerFactory, bizManagerService); assertEquals(ROOT_WEB_CONTEXT_PATH, arkTomcatEmbeddedServletContainerFactory.getContextPath()); BizModel biz = new BizModel(); biz.setBizName("bbb"); biz.setBizState(RESOLVED); biz.setBizVersion("ccc"); biz.setClassLoader(this.getClass().getClassLoader()); bizManagerService.registerBiz(biz); assertEquals(ROOT_WEB_CONTEXT_PATH, arkTomcatEmbeddedServletContainerFactory.getContextPath()); biz.setWebContextPath("/ddd"); currentThread().setContextClassLoader(biz.getBizClassLoader()); assertEquals("/ddd", arkTomcatEmbeddedServletContainerFactory.getContextPath()); arkTomcatEmbeddedServletContainerFactory.setContextPath("/aaa"); assertEquals("/aaa", arkTomcatEmbeddedServletContainerFactory.getContextPath()); } @Test public void testPrepareContext() throws LifecycleException { StandardHost host = new StandardHost(); host.init(); assertEquals(0, host.getChildren().length); arkTomcatEmbeddedServletContainerFactory.setRegisterDefaultServlet(true); currentThread().setContextClassLoader(this.getClass().getClassLoader()); arkTomcatEmbeddedServletContainerFactory.prepareContext(host, new ServletContextInitializer[] {}); assertEquals(1, host.getChildren().length); } @Test public void testOtherMethods() { arkTomcatEmbeddedServletContainerFactory.setBackgroundProcessorDelay(10); arkTomcatEmbeddedServletContainerFactory.setBaseDirectory(null); arkTomcatEmbeddedServletContainerFactory.setProtocol("8888"); } @Test public void testStaticResourceConfigurer() throws Exception { List urls = new ArrayList<>(); urls.add(new URL("file:///aaa.jar!/")); urls.add(new URL("jar:file:///aaa.jar!/")); urls.add(new URL("file:///aaa")); urls.add(new URL("file:///!/aaa!/")); Constructor declaredConstructor = ArkTomcatEmbeddedServletContainerFactory.StaticResourceConfigurer.class .getDeclaredConstructor(ArkTomcatEmbeddedServletContainerFactory.class, Context.class); declaredConstructor.setAccessible(true); ArkTomcatEmbeddedServletContainerFactory.StaticResourceConfigurer staticResourceConfigurer = declaredConstructor .newInstance(arkTomcatEmbeddedServletContainerFactory, new StandardContext()); Method addResourceJars = ArkTomcatEmbeddedServletContainerFactory.StaticResourceConfigurer.class .getDeclaredMethod("addResourceJars", List.class); addResourceJars.setAccessible(true); addResourceJars.invoke(staticResourceConfigurer, urls); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot1/src/test/java/com/alipay/sofa/ark/test/springboot1/IntrospectBizEndpointOnArkDisabledTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.test.springboot1; import com.alipay.sofa.ark.springboot1.web.ArkAutoConfiguration; import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import java.lang.reflect.Field; import java.net.URL; /** * @author qilong.zql * @since 0.6.0 */ public class IntrospectBizEndpointOnArkDisabledTest { @After public void removeTomcatInit() { try { Field urlFactory = URL.class.getDeclaredField("factory"); urlFactory.setAccessible(true); urlFactory.set(null, null); } catch (Throwable t) { // ignore } } @Test public void testIntrospectBizEndpoint() { SpringApplication springApplication = new SpringApplication(EmptyConfiguration.class); ConfigurableApplicationContext applicationContext = springApplication.run(new String[] {}); Assert.assertFalse(applicationContext.containsBean("introspectBizEndpoint")); Assert.assertFalse(applicationContext.containsBean("introspectBizEndpointMvcAdapter")); applicationContext.close(); } @Configuration @EnableAutoConfiguration(exclude = ArkAutoConfiguration.class) static class EmptyConfiguration { } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot1/src/test/java/com/alipay/sofa/ark/test/springboot1/IntrospectBizEndpointOnArkEnabledTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.test.springboot1; import com.alipay.sofa.ark.support.runner.ArkJUnit4EmbedRunner; import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import java.lang.reflect.Field; import java.net.URL; import java.util.HashMap; import java.util.Map; /** * @author qilong.zql * @since 0.6.0 */ @RunWith(ArkJUnit4EmbedRunner.class) public class IntrospectBizEndpointOnArkEnabledTest { @After public void removeTomcatInit() { try { Field urlFactory = URL.class.getDeclaredField("factory"); urlFactory.setAccessible(true); urlFactory.set(null, null); } catch (Throwable t) { // ignore } } @Test public void testIntrospectBizEndpoint() { SpringApplication springApplication = new SpringApplication( IntrospectBizEndpointOnArkDisabledTest.EmptyConfiguration.class); ConfigurableApplicationContext applicationContext = springApplication.run(new String[] {}); Assert.assertTrue(applicationContext.containsBean("introspectBizEndpoint")); Assert.assertTrue(applicationContext.containsBean("introspectBizEndpointMvcAdapter")); applicationContext.close(); } @Test public void testDisableBizStateEndpoint() { Map properties = new HashMap<>(); properties.put("endpoints.bizState.enabled", "false"); SpringApplication springApplication = new SpringApplication( IntrospectBizEndpointOnArkDisabledTest.EmptyConfiguration.class); springApplication.setDefaultProperties(properties); ConfigurableApplicationContext applicationContext = springApplication.run(new String[] {}); Assert.assertFalse(applicationContext.containsBean("introspectBizEndpoint")); Assert.assertFalse(applicationContext.containsBean("introspectBizEndpointMvcAdapter")); applicationContext.close(); } @Configuration @EnableAutoConfiguration static class EmptyConfiguration { } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot1/src/test/resources/logback.xml ================================================ %d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger{36}.%M - %msg%n ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot2/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-compatible-springboot2` **Package**: `com.alipay.sofa.ark.springboot` This module provides Spring Boot 2.x compatibility for SOFAArk. ## Purpose - Enable Ark features in Spring Boot 2.x applications - Version-specific adapter implementations ## Dependencies - `sofa-ark-common-springboot` - Common integration code - Spring Boot 2.x (provided scope) ## Used By - `ark-springboot-starter` - Conditionally loaded for Spring Boot 2.x apps ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot2/pom.xml ================================================ sofa-ark-support com.alipay.sofa ${sofa.ark.version} ../../pom.xml 4.0.0 sofa-ark-compatible-springboot2 ${project.groupId}:${project.artifactId} 2.7.14 org.springframework.boot spring-boot-starter-actuator ${spring.boot.version} provided spring-boot-starter-logging org.springframework.boot org.springframework.boot spring-boot-starter-web ${spring.boot.version} provided com.alipay.sofa sofa-ark-api com.alipay.sofa sofa-ark-common-springboot junit junit test com.alipay.sofa sofa-ark-support-starter test com.alipay.sofa sofa-ark-all ${sofa.ark.version.old} test jdk7 1.7 org.apache.maven.plugins maven-surefire-plugin **/SpringBoot2*Test.java org.apache.maven.surefire surefire-junit47 ${surefire.version} org.apache.maven.surefire surefire-testng ${surefire.version} ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot2/src/main/java/com/alipay/sofa/ark/springboot2/CompatibleSpringBoot2AutoConfiguration.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot2; import com.alipay.sofa.ark.springboot.condition.ConditionalOnArkEnabled; import com.alipay.sofa.ark.springboot.condition.ConditionalOnSpringBootVersion; import com.alipay.sofa.ark.springboot2.endpoint.IntrospectBizEndpoint; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author qilong.zql * @since 0.6.0 */ @Configuration @ConditionalOnSpringBootVersion(version = ConditionalOnSpringBootVersion.Version.TwoX) @ConditionalOnArkEnabled public class CompatibleSpringBoot2AutoConfiguration { @ConditionalOnClass(name = "org.springframework.boot.actuate.endpoint.annotation.Endpoint") public static class ConditionIntrospectEndpointConfiguration { @Bean @ConditionalOnAvailableEndpoint public IntrospectBizEndpoint introspectBizEndpoint() { return new IntrospectBizEndpoint(); } } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot2/src/main/java/com/alipay/sofa/ark/springboot2/endpoint/IntrospectBizEndpoint.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot2.endpoint; import com.alipay.sofa.ark.api.ArkClient; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; /** * @author qilong.zql * @since 0.6.0 */ @Endpoint(id = "bizState") public class IntrospectBizEndpoint { @ReadOperation public Object bizState() { return ArkClient.checkBiz(); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot2/src/main/java/com/alipay/sofa/ark/springboot2/web/SwitchClassLoaderFilter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot2.web; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; public class SwitchClassLoaderFilter implements Filter { /** * using user logger not ArkLogger, to print this log into user log dir */ private Logger logger = LoggerFactory.getLogger(SwitchClassLoaderFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { Filter.super.init(filterConfig); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { ClassLoader oldClassloader = Thread.currentThread().getContextClassLoader(); try { if ("com.alipay.sofa.ark.web.embed.tomcat.ArkTomcatEmbeddedWebappClassLoader" .equals(oldClassloader.getClass().getName())) { ClassLoader bizClassLoader = oldClassloader.getParent(); if (bizClassLoader != null) { logger.debug("switch web classLoader from {} to {}", oldClassloader, bizClassLoader); Thread.currentThread().setContextClassLoader(bizClassLoader); } } filterChain.doFilter(servletRequest, servletResponse); } finally { Thread.currentThread().setContextClassLoader(oldClassloader); } } @Override public void destroy() { Filter.super.destroy(); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot2/src/main/resources/META-INF/spring.factories ================================================ org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.alipay.sofa.ark.springboot2.CompatibleSpringBoot2AutoConfiguration ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot2/src/test/java/com/alipay/sofa/ark/test/springboot2/SpringBoot2IntrospectBizEndpointOnArkDisabledTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.test.springboot2; import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import java.lang.reflect.Field; import java.net.URL; /** * @author qilong.zql * @since 0.6.0 */ public class SpringBoot2IntrospectBizEndpointOnArkDisabledTest { @After public void removeTomcatInit() { try { Field urlFactory = URL.class.getDeclaredField("factory"); urlFactory.setAccessible(true); urlFactory.set(null, null); } catch (Throwable t) { // ignore } } @Test public void testIntrospectBizEndpoint() { SpringApplication springApplication = new SpringApplication(EmptyConfiguration.class); ConfigurableApplicationContext applicationContext = springApplication.run(new String[] {}); Assert.assertFalse(applicationContext.containsBean("introspectBizEndpoint")); applicationContext.close(); } @Configuration @EnableAutoConfiguration static class EmptyConfiguration { } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot2/src/test/java/com/alipay/sofa/ark/test/springboot2/SpringBoot2IntrospectBizEndpointOnArkEnabledTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.test.springboot2; import com.alipay.sofa.ark.support.runner.ArkJUnit4EmbedRunner; import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import java.lang.reflect.Field; import java.net.URL; import java.util.HashMap; import java.util.Map; /** * @author qilong.zql * @since 0.6.0 */ @RunWith(ArkJUnit4EmbedRunner.class) public class SpringBoot2IntrospectBizEndpointOnArkEnabledTest { @After public void removeTomcatInit() { try { Field urlFactory = URL.class.getDeclaredField("factory"); urlFactory.setAccessible(true); urlFactory.set(null, null); } catch (Throwable t) { // ignore } } @Test public void testIntrospectBizEndpoint() { Map properties = new HashMap<>(); properties.put("management.endpoints.web.exposure.include", "*"); SpringApplication springApplication = new SpringApplication(EmptyConfiguration.class); springApplication.setDefaultProperties(properties); ConfigurableApplicationContext applicationContext = springApplication.run(new String[] {}); Assert.assertTrue(applicationContext.containsBean("introspectBizEndpoint")); applicationContext.close(); } @Test public void testDisableBizStateEndpoint() { Map properties = new HashMap<>(); properties.put("management.endpoint.bizState.enabled", "false"); SpringApplication springApplication = new SpringApplication(EmptyConfiguration.class); springApplication.setDefaultProperties(properties); ConfigurableApplicationContext applicationContext = springApplication.run(new String[] {}); Assert.assertFalse(applicationContext.containsBean("introspectBizEndpoint")); applicationContext.close(); } @Configuration @EnableAutoConfiguration static class EmptyConfiguration { } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-compatible-springboot2/src/test/resources/logback.xml ================================================ %d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger{36}.%M - %msg%n ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-springboot-starter` **Package**: `com.alipay.sofa.ark.springboot` This is the main Spring Boot Starter for SOFAArk. It provides auto-configuration and integration with Spring Boot applications. ## Purpose - Auto-configure SOFAArk in Spring Boot applications - Provide Spring Boot style integration - Enable Ark features with minimal configuration ## Key Features ### Auto-Configuration - Automatically activates when SOFAArk container is running - Conditionally loads version-specific compatibility modules - Exposes Ark services as Spring beans ### Integration Points - `ApplicationContext` integration with Ark Biz - Spring Environment bridging - Event system integration ## Usage Add dependency: ```xml com.alipay.sofa sofa-ark-springboot-starter ``` ## Dependencies - `sofa-ark-common-springboot` - Common integration - `sofa-ark-compatible-springboot1` - Spring Boot 1.x support (optional) - `sofa-ark-compatible-springboot2` - Spring Boot 2.x support (optional) - `sofa-ark-support-starter` - Startup support ## Used By - Spring Boot applications running on SOFAArk ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/pom.xml ================================================ sofa-ark-support com.alipay.sofa ${sofa.ark.version} ../../pom.xml 4.0.0 2.7.14 sofa-ark-springboot-starter ${project.groupId}:${project.artifactId} org.springframework.boot spring-boot ${spring.boot.version} provided org.springframework.boot spring-boot-actuator ${spring.boot.version} provided org.springframework.boot spring-boot-starter-web ${spring.boot.version} provided org.springframework.boot spring-boot-autoconfigure ${spring.boot.version} provided com.alipay.sofa sofa-ark-support-starter com.alipay.sofa sofa-ark-api com.alipay.sofa sofa-ark-compatible-springboot1 com.alipay.sofa sofa-ark-compatible-springboot2 org.springframework.boot spring-boot-starter-test ${spring.boot.version} test org.springframework.boot spring-boot-starter-logging ${spring.boot.version} test log4j-over-slf4j org.slf4j logback-classic ch.qos.logback com.alipay.sofa sofa-ark-container test com.alipay.sofa sofa-ark-all ${sofa.ark.version.old} test com.alipay.sofa web-ark-plugin test junit junit provided org.testng testng provided org.apache.logging.log4j log4j-slf4j-impl 2.17.1 test org.springframework.boot spring-boot-loader io.projectreactor.netty reactor-netty ${reactor-netty.version} provided org.mockito mockito-inline ${mockito.version} test org.apache.maven.plugins maven-surefire-plugin 1 false org.apache.maven.surefire surefire-junit47 ${surefire.version} org.apache.maven.surefire surefire-testng ${surefire.version} ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/ArkAutoProcessorConfiguration.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot; import com.alipay.sofa.ark.springboot.condition.ConditionalOnArkEnabled; import com.alipay.sofa.ark.springboot.processor.ArkEventHandlerProcessor; import com.alipay.sofa.ark.springboot.processor.ArkServiceInjectProcessor; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author qixiaobo * @since 2.2.4 */ @Configuration @ConditionalOnArkEnabled @AutoConfigureBefore(name = { "org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration", "org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration" }) public class ArkAutoProcessorConfiguration { @Bean public static ArkServiceInjectProcessor serviceInjectProcessor() { return new ArkServiceInjectProcessor(); } @Bean public static ArkEventHandlerProcessor arkEventHandlerProcessor() { return new ArkEventHandlerProcessor(); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/ArkReactiveAutoConfiguration.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot; import com.alipay.sofa.ark.springboot.condition.ConditionalOnArkEnabled; import com.alipay.sofa.ark.springboot.web.ArkNettyReactiveWebServerFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.boot.web.embedded.netty.NettyRouteProvider; import org.springframework.boot.web.embedded.netty.NettyServerCustomizer; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.reactive.ReactorResourceFactory; import reactor.netty.http.server.HttpServer; import java.util.Collection; import java.util.stream.Collectors; @Configuration @ConditionalOnArkEnabled @AutoConfigureBefore(name = { "org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration" }) public class ArkReactiveAutoConfiguration { @Configuration @ConditionalOnMissingBean({ ReactiveWebServerFactory.class }) @ConditionalOnClass(value = { HttpServer.class }, name = { "com.alipay.sofa.ark.netty.ArkNettyIdentification" }) static class EmbeddedNetty { EmbeddedNetty() { } @Bean @ConditionalOnMissingBean ReactorResourceFactory reactorServerResourceFactory() { ReactorResourceFactory reactorResourceFactory = new ReactorResourceFactory(); reactorResourceFactory.setUseGlobalResources(false); return reactorResourceFactory; } @Bean NettyReactiveWebServerFactory nettyReactiveWebServerFactory(ReactorResourceFactory resourceFactory, ObjectProvider routes, ObjectProvider serverCustomizers) { NettyReactiveWebServerFactory serverFactory = new ArkNettyReactiveWebServerFactory(); serverFactory.setResourceFactory(resourceFactory); routes.orderedStream().forEach((xva$0) -> { serverFactory.addRouteProviders(new NettyRouteProvider[]{xva$0}); }); serverFactory.getServerCustomizers().addAll((Collection)serverCustomizers.orderedStream().collect(Collectors.toList())); return serverFactory; } } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/ArkServletAutoConfiguration.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot; import com.alipay.sofa.ark.springboot.condition.ConditionalOnArkEnabled; import com.alipay.sofa.ark.springboot.web.ArkTomcatServletWebServerFactory; import org.apache.catalina.startup.Tomcat; import org.apache.coyote.UpgradeProtocol; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Servlet; import java.util.stream.Collectors; /** * @author qilong.zql * @since 0.6.0 */ @Configuration @ConditionalOnArkEnabled @ConditionalOnClass({ ServletWebServerFactoryAutoConfiguration.class, TomcatProtocolHandlerCustomizer.class }) @AutoConfigureBefore(ServletWebServerFactoryAutoConfiguration.class) public class ArkServletAutoConfiguration { @Configuration @ConditionalOnClass(value = { Servlet.class, Tomcat.class, UpgradeProtocol.class, ServletWebServerFactory.class }, name = { "com.alipay.sofa.ark.web.embed.tomcat.ArkTomcatEmbeddedWebappClassLoader" }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedArkTomcat { @Bean @ConditionalOnMissingBean(ArkTomcatServletWebServerFactory.class) public TomcatServletWebServerFactory tomcatServletWebServerFactory(ObjectProvider connectorCustomizers, ObjectProvider contextCustomizers, ObjectProvider> protocolHandlerCustomizers) { ArkTomcatServletWebServerFactory factory = new ArkTomcatServletWebServerFactory(); factory.getTomcatConnectorCustomizers().addAll( connectorCustomizers.orderedStream().collect(Collectors.toList())); factory.getTomcatContextCustomizers().addAll( contextCustomizers.orderedStream().collect(Collectors.toList())); factory.getTomcatProtocolHandlerCustomizers().addAll( protocolHandlerCustomizers.orderedStream().collect(Collectors.toList())); return factory; } } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/ArkServletLegacyAutoConfiguration.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot; import com.alipay.sofa.ark.springboot.condition.ConditionalOnArkEnabled; import com.alipay.sofa.ark.springboot.web.ArkTomcatServletWebServerFactory; import org.apache.catalina.startup.Tomcat; import org.apache.coyote.UpgradeProtocol; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Servlet; /** * adapt to springboot 2.1.9.RELEASE * @author gaosaroma * @since 2.2.8 */ @Configuration @ConditionalOnArkEnabled @ConditionalOnClass(ServletWebServerFactoryAutoConfiguration.class) @ConditionalOnMissingClass("org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer") @AutoConfigureBefore(ServletWebServerFactoryAutoConfiguration.class) public class ArkServletLegacyAutoConfiguration { @Configuration @ConditionalOnClass(value = { Servlet.class, Tomcat.class, UpgradeProtocol.class, ServletWebServerFactory.class }, name = { "com.alipay.sofa.ark.web.embed.tomcat.ArkTomcatEmbeddedWebappClassLoader" }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedArkTomcat { @Bean @ConditionalOnMissingBean(ArkTomcatServletWebServerFactory.class) public TomcatServletWebServerFactory tomcatServletWebServerFactory() { return new ArkTomcatServletWebServerFactory(); } } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/listener/ArkApplicationStartListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.listener; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AfterFinishDeployEvent; import com.alipay.sofa.ark.spi.event.AfterFinishStartupEvent; import com.alipay.sofa.ark.spi.event.biz.AfterBizStartupEvent; import com.alipay.sofa.ark.support.common.MasterBizEnvironmentHolder; import com.alipay.sofa.ark.support.startup.EmbedSofaArkBootstrap; import com.alipay.sofa.ark.support.startup.SofaArkBootstrap; import org.springframework.boot.SpringBootVersion; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.SpringApplicationEvent; import org.springframework.context.ApplicationListener; /** * Ark Spring boot starter when run on ide * * @author ruoshan * @since 0.1.0 */ public class ArkApplicationStartListener implements ApplicationListener { private static final String LAUNCH_CLASSLOADER_NAME = "sun.misc.Launcher$AppClassLoader"; private static final String SPRING_BOOT_LOADER = "org.springframework.boot.loader.LaunchedURLClassLoader"; //SpringBoot 3.2.0 support private static final String SPRING_BOOT_NEW_LOADER = "org.springframework.boot.loader.launch.LaunchedClassLoader"; private static final String APPLICATION_STARTED_EVENT = "org.springframework.boot.context.event.ApplicationStartedEvent"; private static final String APPLICATION_STARTING_EVENT = "org.springframework.boot.context.event.ApplicationStartingEvent"; private static Class SPRING_BOOT_LOADER_CLASS; private static Class SPRING_BOOT_NEW_LOADER_CLASS; static { try { SPRING_BOOT_LOADER_CLASS = ApplicationListener.class.getClassLoader().loadClass( SPRING_BOOT_LOADER); } catch (Throwable t) { // ignore } try { SPRING_BOOT_NEW_LOADER_CLASS = ApplicationListener.class.getClassLoader().loadClass( SPRING_BOOT_NEW_LOADER); } catch (Throwable t) { // ignore } } @Override public void onApplicationEvent(SpringApplicationEvent event) { try { if (isEmbedEnable()) { ArkConfigs.setEmbedEnable(true); startUpArkEmbed(event); return; } if (isSpringBoot2() && APPLICATION_STARTING_EVENT.equals(event.getClass().getCanonicalName())) { startUpArk(event); } if (isSpringBoot1() && APPLICATION_STARTED_EVENT.equals(event.getClass().getCanonicalName())) { startUpArk(event); } } catch (Throwable e) { throw new RuntimeException("Meet exception when determine whether to start SOFAArk!", e); } } private boolean isEmbedEnable() { if (ArkConfigs.isEmbedEnable()) { return true; } if (SPRING_BOOT_LOADER_CLASS != null && SPRING_BOOT_LOADER_CLASS.isAssignableFrom(this.getClass().getClassLoader() .getClass())) { return true; } if (SPRING_BOOT_NEW_LOADER_CLASS != null && SPRING_BOOT_NEW_LOADER_CLASS.isAssignableFrom(this.getClass().getClassLoader() .getClass())) { return true; } return false; } public void startUpArk(SpringApplicationEvent event) { if (LAUNCH_CLASSLOADER_NAME.equals(this.getClass().getClassLoader().getClass().getName())) { SofaArkBootstrap.launch(event.getArgs()); } } public boolean isSpringBoot1() { String version = SpringBootVersion.getVersion(); return null == version ? false : version.startsWith("1"); } public boolean isSpringBoot2() { String version = SpringBootVersion.getVersion(); return null == version ? false : version.startsWith("2"); } protected void startUpArkEmbed(SpringApplicationEvent event) { // 仅监听基座启动时的Event if (this.getClass().getClassLoader() != Thread.currentThread().getContextClassLoader()) { return; } if (event instanceof ApplicationEnvironmentPreparedEvent) { ApplicationEnvironmentPreparedEvent preparedEvent = (ApplicationEnvironmentPreparedEvent) event; MasterBizEnvironmentHolder.setEnvironment(preparedEvent.getEnvironment()); EmbedSofaArkBootstrap.launch(preparedEvent.getEnvironment()); } if (event instanceof ApplicationReadyEvent) { // 基座启动后+静态合并部署的biz启动后,发送事件 sendEventAfterArkEmbedStartupFinish(); } } protected void sendEventAfterArkEmbedStartupFinish() { if (ArkClient.getEventAdminService() != null && ArkClient.getMasterBiz() != null) { ArkClient.getEventAdminService().sendEvent( new AfterBizStartupEvent(ArkClient.getMasterBiz())); ArkClient.getEventAdminService().sendEvent(new AfterFinishDeployEvent()); ArkClient.getEventAdminService().sendEvent(new AfterFinishStartupEvent()); } } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/listener/ArkDeployStaticBizListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.listener; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.support.startup.EmbedSofaArkBootstrap; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ApplicationContextEvent; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.Ordered; import java.util.concurrent.atomic.AtomicBoolean; public class ArkDeployStaticBizListener implements ApplicationListener, Ordered { private static final AtomicBoolean deployed = new AtomicBoolean(false); @Override public void onApplicationEvent(ApplicationContextEvent event) { // Only listen to the event when the master biz is started if (this.getClass().getClassLoader() != Thread.currentThread().getContextClassLoader()) { return; } if (ArkConfigs.isEmbedEnable() && ArkConfigs.isEmbedStaticBizEnable()) { if (event instanceof ContextRefreshedEvent && deployed.compareAndSet(false, true) && event.getApplicationContext().getParent() == null) { // After the master biz is started, statically deploy the other biz from classpath EmbedSofaArkBootstrap.deployStaticBizAfterEmbedMasterBizStarted(); } } } @Override public int getOrder() { return LOWEST_PRECEDENCE - 10; } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/listener/PropertiesResetListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.listener; import com.alipay.sofa.ark.api.ArkConfigs; import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.Ordered; /** * Listener to reset system properties * * @author bingjie.lbj */ public class PropertiesResetListener implements ApplicationListener, Ordered { @Override public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { if (ArkConfigs.isEmbedEnable()) { System.getProperties().remove("logging.path"); } } @Override public int getOrder() { //after ConfigFileApplicationListener return ConfigDataEnvironmentPostProcessor.ORDER + 1; } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/loader/CachedLaunchedURLClassLoader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.loader; import org.springframework.boot.loader.LaunchedURLClassLoader; import org.springframework.boot.loader.archive.Archive; import java.io.IOException; import java.net.URL; import java.util.Enumeration; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; /** * A cached LaunchedURLClassLoader to accelerate load classes and resources. * NOTE: * 1. Not found classes will be cached. * 2. If findResources(name) return null Enumeration, then it will be cached. * 3. findResource(name) will always be cached no matter it was found or not found. * 4. Otherwise, classes or resources-enumeration will not be cached explicitly. * * @author bingjie.lbj */ public class CachedLaunchedURLClassLoader extends LaunchedURLClassLoader { private final Map classCache = new ConcurrentHashMap<>( 3000); private final Map> resourceUrlCache = new ConcurrentHashMap<>( 3000); private final Map>> resourcesUrlCache = new ConcurrentHashMap<>( 300); static { ClassLoader.registerAsParallelCapable(); } public CachedLaunchedURLClassLoader(boolean exploded, Archive rootArchive, URL[] urls, ClassLoader parent) { super(exploded, rootArchive, urls, parent); } @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { return loadClassWithCache(name, resolve); } @Override public URL findResource(String name) { Optional urlOptional = resourceUrlCache.get(name); if (urlOptional != null) { return urlOptional.orElse(null); } URL url = super.findResource(name); resourceUrlCache.put(name, url != null ? Optional.of(url) : Optional.empty()); return url; } @Override public Enumeration findResources(String name) throws IOException { Optional> urlOptional = resourcesUrlCache.get(name); if (urlOptional != null) { return urlOptional.orElse(null); } Enumeration enumeration = super.findResources(name); if (enumeration == null || !enumeration.hasMoreElements()) { resourcesUrlCache.put(name, Optional.empty()); } return enumeration; } /** * NOTE: Only cache ClassNotFoundException when class not found. * If class found, do not cache, and just use parent class loader cache. */ protected Class loadClassWithCache(String name, boolean resolve) throws ClassNotFoundException { LoadClassResult resultInCache = classCache.get(name); if (resultInCache != null) { if (resultInCache.getEx() != null) { throw resultInCache.getEx(); } return resultInCache.getClazz(); } try { Class clazz = super.findLoadedClass(name); if (clazz == null) { clazz = super.loadClass(name, resolve); } if (clazz == null) { classCache.put(name, LoadClassResult.NOT_FOUND); } return clazz; } catch (ClassNotFoundException exception) { classCache.put(name, LoadClassResult.NOT_FOUND); throw exception; } } protected static class LoadClassResult { private Class clazz; private ClassNotFoundException ex; protected static LoadClassResult NOT_FOUND = new LoadClassResult( new ClassNotFoundException()); public LoadClassResult() { } public LoadClassResult(ClassNotFoundException ex) { this.ex = ex; } public Class getClazz() { return clazz; } public void setClazz(Class clazz) { this.clazz = clazz; } public ClassNotFoundException getEx() { return ex; } public void setEx(ClassNotFoundException ex) { this.ex = ex; } } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/loader/JarLauncher.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.loader; import java.net.URL; /** * A JarLauncher to load classes with CachedLaunchedURLClassLoader * * @author bingjie.lbj */ public class JarLauncher extends org.springframework.boot.loader.JarLauncher { public static void main(String[] args) throws Exception { new JarLauncher().launch(args); } @Override protected ClassLoader createClassLoader(URL[] urls) throws Exception { return new CachedLaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass() .getClassLoader()); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/processor/ArkEventHandlerProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.processor; import com.alipay.sofa.ark.spi.service.ArkInject; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import com.alipay.sofa.ark.spi.service.event.EventHandler; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.core.Ordered; /** * @author qilong.zql * @since 0.6.0 */ public class ArkEventHandlerProcessor implements BeanPostProcessor, Ordered { @ArkInject private EventAdminService eventAdminService; @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof EventHandler) { eventAdminService.register((EventHandler) bean); } return bean; } @Override public int getOrder() { return LOWEST_PRECEDENCE; } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/processor/ArkServiceInjectProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.processor; import com.alipay.sofa.ark.api.ArkClient; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.core.PriorityOrdered; /** * @author qilong.zql * @since 0.6.0 */ public class ArkServiceInjectProcessor implements BeanPostProcessor, PriorityOrdered { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { ArkClient.getInjectionService().inject(bean); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public int getOrder() { return LOWEST_PRECEDENCE; } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/runner/ArkBootEmbedRunner.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.runner; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.support.common.DelegateArkContainer; import com.alipay.sofa.ark.support.runner.JUnitExecutionListener; import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.manipulation.Filter; import org.junit.runner.manipulation.Filterable; import org.junit.runner.manipulation.NoTestsRemainException; import org.junit.runner.manipulation.Sortable; import org.junit.runner.manipulation.Sorter; import org.junit.runner.notification.RunNotifier; import static com.alipay.sofa.ark.spi.constant.Constants.EMBED_ENABLE; import static com.alipay.sofa.ark.spi.constant.Constants.MASTER_BIZ; /** * Corresponding to {@literal org.springframework.test.context.junit4.SpringRunner} * used for test ark biz like koupleless, which run ark plugin in embed mode * please refer {@link com.alipay.sofa.ark.spi.service.plugin.PluginFactoryService#createEmbedPlugin(com.alipay.sofa.ark.spi.archive.PluginArchive, java.lang.ClassLoader)} * * @author lvjing2 * @since 2.2.10 */ public class ArkBootEmbedRunner extends Runner implements Filterable, Sortable { private static final String SPRING_RUNNER = "org.springframework.test.context.junit4.SpringRunner"; /** * {@see org.springframework.test.context.junit4.SpringRunner} */ private Runner runner; @SuppressWarnings("unchecked") public ArkBootEmbedRunner(Class klazz) { if (!DelegateArkContainer.isStarted()) { System.setProperty(EMBED_ENABLE, "true"); System.setProperty(MASTER_BIZ, "test master biz"); DelegateArkContainer.launch(klazz); System.clearProperty(EMBED_ENABLE); System.clearProperty(MASTER_BIZ); } Class springRunnerClass = DelegateArkContainer.loadClass(SPRING_RUNNER); Class testClass = DelegateArkContainer.loadClass(klazz.getName()); try { runner = (Runner) springRunnerClass.getConstructor(Class.class).newInstance(testClass); } catch (Exception ex) { throw new RuntimeException(ex); } } @Override public Description getDescription() { return runner.getDescription(); } @Override public void run(RunNotifier notifier) { ClassLoader oldClassLoader = ClassLoaderUtils.pushContextClassLoader(DelegateArkContainer .getTestClassLoader()); try { notifier.addListener(JUnitExecutionListener.getRunListener()); runner.run(notifier); } finally { ClassLoaderUtils.popContextClassLoader(oldClassLoader); } } @Override public void filter(Filter filter) throws NoTestsRemainException { ((Filterable) runner).filter(filter); } @Override public void sort(Sorter sorter) { ((Sortable) runner).sort(sorter); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/runner/ArkBootRunner.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.runner; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.support.common.DelegateArkContainer; import com.alipay.sofa.ark.support.runner.JUnitExecutionListener; import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.manipulation.*; import org.junit.runner.notification.RunNotifier; /** * Corresponding to {@literal org.springframework.test.context.junit4.SpringRunner} * * used for test ark plugin * * @author qilong.zql * @since 0.1.0 */ public class ArkBootRunner extends Runner implements Filterable, Sortable { private static final String SPRING_RUNNER = "org.springframework.test.context.junit4.SpringRunner"; /** * {@see org.springframework.test.context.junit4.SpringRunner} */ private Runner runner; @SuppressWarnings("unchecked") public ArkBootRunner(Class klazz) { if (!DelegateArkContainer.isStarted()) { DelegateArkContainer.launch(klazz); } Class springRunnerClass = DelegateArkContainer.loadClass(SPRING_RUNNER); Class testClass = DelegateArkContainer.loadClass(klazz.getName()); try { runner = (Runner) springRunnerClass.getConstructor(Class.class).newInstance(testClass); } catch (Exception ex) { throw new RuntimeException(ex); } } @Override public Description getDescription() { return runner.getDescription(); } @Override public void run(RunNotifier notifier) { ClassLoader oldClassLoader = ClassLoaderUtils.pushContextClassLoader(DelegateArkContainer .getTestClassLoader()); try { notifier.addListener(JUnitExecutionListener.getRunListener()); runner.run(notifier); } finally { ClassLoaderUtils.popContextClassLoader(oldClassLoader); } } @Override public void filter(Filter filter) throws NoTestsRemainException { ((Filterable) runner).filter(filter); } @Override public void sort(Sorter sorter) { ((Sortable) runner).sort(sorter); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/web/ArkCompositeReactorHttpHandlerAdapter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.web; import com.alipay.sofa.ark.exception.ArkRuntimeException; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter; import reactor.core.publisher.Mono; import reactor.netty.http.server.HttpServerRequest; import reactor.netty.http.server.HttpServerResponse; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @author: yuanyuan */ public class ArkCompositeReactorHttpHandlerAdapter extends ReactorHttpHandlerAdapter { private Map bizReactorHttpHandlerAdapters = new ConcurrentHashMap<>(); public ArkCompositeReactorHttpHandlerAdapter(HttpHandler httpHandler) { super(httpHandler); } @Override public Mono apply(HttpServerRequest reactorRequest, HttpServerResponse reactorResponse) { String uri = reactorRequest.uri(); for (Map.Entry entry : bizReactorHttpHandlerAdapters .entrySet()) { if (uri.startsWith(entry.getKey())) { ReactorHttpHandlerAdapter adapter = entry.getValue(); return adapter.apply(reactorRequest, reactorResponse); } } return super.apply(reactorRequest, reactorResponse); } public void registerBizReactorHttpHandlerAdapter(String contextPath, ReactorHttpHandlerAdapter reactorHttpHandlerAdapter) { ReactorHttpHandlerAdapter old = bizReactorHttpHandlerAdapters.putIfAbsent(contextPath, reactorHttpHandlerAdapter); if (old != null) { throw new ArkRuntimeException("Duplicated context path"); } } public void unregisterBizReactorHttpHandlerAdapter(String contextPath) { bizReactorHttpHandlerAdapters.remove(contextPath); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/web/ArkNettyReactiveWebServerFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.web; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.service.ArkInject; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.web.EmbeddedServerService; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.boot.web.embedded.netty.NettyRouteProvider; import org.springframework.boot.web.embedded.netty.NettyServerCustomizer; import org.springframework.boot.web.embedded.netty.SslServerCustomizer; import org.springframework.boot.web.server.WebServer; import org.springframework.http.client.reactive.ReactorResourceFactory; import org.springframework.http.server.reactive.ContextPathCompositeHandler; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import reactor.netty.http.HttpProtocol; import reactor.netty.http.server.HttpServer; import reactor.netty.resources.LoopResources; import java.net.InetSocketAddress; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import static com.alipay.sofa.ark.spi.constant.Constants.ROOT_WEB_CONTEXT_PATH; public class ArkNettyReactiveWebServerFactory extends NettyReactiveWebServerFactory { private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private Duration lifecycleTimeout; private List routeProviders = new ArrayList(); @ArkInject private EmbeddedServerService embeddedNettyService; @ArkInject private BizManagerService bizManagerService; private boolean useForwardHeaders; private ReactorResourceFactory resourceFactory; private int backgroundProcessorDelay; private Set serverCustomizers = new LinkedHashSet(); private static ArkCompositeReactorHttpHandlerAdapter adapter; public ArkNettyReactiveWebServerFactory() { } @Override public WebServer getWebServer(HttpHandler httpHandler) { if (embeddedNettyService == null && ArkClient.getInjectionService() != null) { // 非应用上下文 (例如: Spring Management Context) 没有经历 Start 生命周期, 不会被注入 ArkServiceInjectProcessor, // 因此 @ArkInject 没有被处理, 需要手动处理 ArkClient.getInjectionService().inject(this); } if (embeddedNettyService == null) { // 原有的逻辑中也有这个空值判断, 不确定注入后是否还会有用例会导致 embeddedNettyService 为空 // 因此仍保留此 if 空值判断 return super.getWebServer(httpHandler); } if (embeddedNettyService.getEmbedServer(getPort()) == null) { embeddedNettyService.putEmbedServer(getPort(), initEmbedNetty()); } String contextPath = getContextPath(); Map handlerMap = new HashMap<>(); handlerMap.put(contextPath, httpHandler); ContextPathCompositeHandler contextHandler = new ContextPathCompositeHandler(handlerMap); if (adapter == null) { adapter = new ArkCompositeReactorHttpHandlerAdapter(contextHandler); } else { adapter.registerBizReactorHttpHandlerAdapter(contextPath, new ReactorHttpHandlerAdapter(contextHandler)); } HttpServer httpServer = (HttpServer) embeddedNettyService.getEmbedServer(getPort()); ArkNettyWebServer webServer = (ArkNettyWebServer) createNettyWebServer(contextPath, httpServer, adapter, lifecycleTimeout); webServer.setRouteProviders(this.routeProviders); return webServer; } public String getContextPath() { String contextPath = ""; if (bizManagerService == null) { return contextPath; } Biz biz = bizManagerService.getBizByClassLoader(Thread.currentThread() .getContextClassLoader()); if (!StringUtils.isEmpty(contextPath)) { return contextPath; } else if (biz != null) { if (StringUtils.isEmpty(biz.getWebContextPath())) { return ROOT_WEB_CONTEXT_PATH; } contextPath = biz.getWebContextPath(); if (!contextPath.startsWith("/")) { contextPath = "/" + contextPath; } return contextPath; } else { return ROOT_WEB_CONTEXT_PATH; } } WebServer createNettyWebServer(String contextPath, HttpServer httpServer, ReactorHttpHandlerAdapter handlerAdapter, Duration lifecycleTimeout) { return new ArkNettyWebServer(contextPath, httpServer, handlerAdapter, lifecycleTimeout); } private HttpServer initEmbedNetty(){ HttpServer server = HttpServer.create(); if (this.resourceFactory != null) { LoopResources resources = this.resourceFactory.getLoopResources(); Assert.notNull(resources, "No LoopResources: is ReactorResourceFactory not initialized yet?"); server = server.tcpConfiguration((tcpServer) -> tcpServer.runOn(resources)).bindAddress(this::getListenAddress); } else { server = server.bindAddress(this::getListenAddress); } if (this.getSsl() != null && this.getSsl().isEnabled()) { server = this.customizeSslConfiguration(server); } server = server.protocol(this.listProtocols()).forwarded(this.useForwardHeaders); return applyCustomizers(server); } private HttpServer customizeSslConfiguration(HttpServer httpServer) { SslServerCustomizer sslServerCustomizer = new SslServerCustomizer(this.getSsl(), this.getHttp2(), this.getSslStoreProvider()); return sslServerCustomizer.apply(httpServer); } private HttpProtocol[] listProtocols() { List protocols = new ArrayList(); protocols.add(HttpProtocol.HTTP11); if (this.getHttp2() != null && this.getHttp2().isEnabled()) { if (this.getSsl() != null && this.getSsl().isEnabled()) { protocols.add(HttpProtocol.H2); } else { protocols.add(HttpProtocol.H2C); } } return (HttpProtocol[]) protocols.toArray(new HttpProtocol[0]); } private HttpServer applyCustomizers(HttpServer server) { NettyServerCustomizer customizer; for (Iterator var2 = this.serverCustomizers.iterator(); var2.hasNext(); server = (HttpServer) customizer .apply(server)) { customizer = (NettyServerCustomizer) var2.next(); } return server; } private InetSocketAddress getListenAddress() { return this.getAddress() != null ? new InetSocketAddress( this.getAddress().getHostAddress(), this.getPort()) : new InetSocketAddress( this.getPort()); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/web/ArkNettyWebServer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.web; import io.netty.channel.group.DefaultChannelGroup; import io.netty.channel.unix.Errors; import io.netty.util.concurrent.DefaultEventExecutor; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; import org.springframework.boot.web.embedded.netty.NettyRouteProvider; import org.springframework.boot.web.server.PortInUseException; import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServerException; import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter; import org.springframework.util.Assert; import reactor.netty.ChannelBindException; import reactor.netty.DisposableServer; import reactor.netty.http.server.HttpServer; import reactor.netty.http.server.HttpServerRequest; import reactor.netty.http.server.HttpServerResponse; import reactor.netty.http.server.HttpServerRoutes; import java.time.Duration; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.function.Supplier; public class ArkNettyWebServer implements WebServer { private static final Predicate ALWAYS = (request) -> { return true; }; private static HttpServer arkHttpServer; private static final Log logger = LogFactory.getLog(ArkNettyWebServer.class); private final HttpServer httpServer; private final BiFunction> handler; private final Duration lifecycleTimeout; private List routeProviders = Collections.emptyList(); private static volatile DisposableServer disposableServer; private Thread awaitThread; private String contextPath; public ArkNettyWebServer(String contextPath, HttpServer httpServer, ReactorHttpHandlerAdapter handlerAdapter, Duration lifecycleTimeout) { Assert.notNull(httpServer, "HttpServer must not be null"); Assert.notNull(handlerAdapter, "HandlerAdapter must not be null"); this.contextPath = contextPath; this.lifecycleTimeout = lifecycleTimeout; this.handler = handlerAdapter; this.httpServer = httpServer.channelGroup(new DefaultChannelGroup(new DefaultEventExecutor())); if (arkHttpServer == null) { arkHttpServer = this.httpServer; } } public void setRouteProviders(List routeProviders) { this.routeProviders = routeProviders; } @Override public void start() throws WebServerException { if (disposableServer == null) { try { disposableServer = this.startHttpServer(); } catch (Exception var2) { PortInUseException.ifCausedBy(var2, ChannelBindException.class, (bindException) -> { if (bindException.localPort() > 0 && !this.isPermissionDenied(bindException.getCause())) { throw new PortInUseException(bindException.localPort(), var2); } }); throw new WebServerException("Unable to start Netty", var2); } this.startDaemonAwaitThread(disposableServer); } if (disposableServer != null) { logger.info("Netty started" + this.getStartedOnMessage(disposableServer) + " with context path " + contextPath); } } @Override public void stop() throws WebServerException { if (!(this.handler instanceof ArkCompositeReactorHttpHandlerAdapter)) { return; } ((ArkCompositeReactorHttpHandlerAdapter) this.handler).unregisterBizReactorHttpHandlerAdapter(contextPath); if (disposableServer != null && this.httpServer == arkHttpServer) { try { if (this.lifecycleTimeout != null) { disposableServer.disposeNow(this.lifecycleTimeout); } else { disposableServer.disposeNow(); } awaitThread.stop(); } catch (IllegalStateException ignore) { } logger.info("Netty stoped" + this.getStartedOnMessage(disposableServer)); disposableServer = null; } } @Override public int getPort() { if (disposableServer != null) { try { return disposableServer.port(); } catch (UnsupportedOperationException var2) { return -1; } } else { return -1; } } private String getStartedOnMessage(DisposableServer server) { StringBuilder message = new StringBuilder(); this.tryAppend(message, "port %s", server::port); this.tryAppend(message, "host %s", server::host); return message.length() > 0 ? " on " + message : ""; } private void tryAppend(StringBuilder message, String format, Supplier supplier) { try { Object value = supplier.get(); message.append(message.length() != 0 ? " " : ""); message.append(String.format(format, value)); } catch (UnsupportedOperationException var5) { } } DisposableServer startHttpServer() { HttpServer server = this.httpServer; if (this.routeProviders.isEmpty()) { server = server.handle(this.handler); } else { server = server.route(this::applyRouteProviders); } return this.lifecycleTimeout != null ? server.bindNow(this.lifecycleTimeout) : server.bindNow(); } private boolean isPermissionDenied(Throwable bindExceptionCause) { try { if (bindExceptionCause instanceof Errors.NativeIoException) { return ((Errors.NativeIoException)bindExceptionCause).expectedErr() == -13; } } catch (Throwable var3) { } return false; } private void applyRouteProviders(HttpServerRoutes routes) { NettyRouteProvider provider; for(Iterator var2 = this.routeProviders.iterator(); var2.hasNext(); routes = (HttpServerRoutes)provider.apply(routes)) { provider = (NettyRouteProvider)var2.next(); } routes.route(ALWAYS, this.handler); } private void startDaemonAwaitThread(DisposableServer disposableServer) { awaitThread = new Thread("server") { public void run() { disposableServer.onDispose().block(); } }; awaitThread.setContextClassLoader(this.getClass().getClassLoader()); awaitThread.setDaemon(false); awaitThread.start(); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/web/ArkTomcatServletWebServerFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.web; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.service.ArkInject; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.web.EmbeddedServerService; import org.apache.catalina.Context; import org.apache.catalina.Engine; import org.apache.catalina.Host; import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleEvent; import org.apache.catalina.LifecycleListener; import org.apache.catalina.LifecycleState; import org.apache.catalina.Valve; import org.apache.catalina.WebResourceRoot; import org.apache.catalina.Wrapper; import org.apache.catalina.connector.Connector; import org.apache.catalina.core.StandardContext; import org.apache.catalina.loader.WebappLoader; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.webresources.StandardRoot; import org.apache.tomcat.util.scan.StandardJarScanFilter; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatWebServer; import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import javax.servlet.ServletContainerInitializer; import java.io.File; import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Locale; import java.util.Map; import static com.alipay.sofa.ark.spi.constant.Constants.ROOT_WEB_CONTEXT_PATH; /** * @author Phillip Webb * @author Dave Syer * @author Brock Mills * @author Stephane Nicoll * @author Andy Wilkinson * @author Eddú Meléndez * @author Christoffer Sawicki * @author qilong.zql * @since 0.6.0 */ public class ArkTomcatServletWebServerFactory extends TomcatServletWebServerFactory { private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private final Object lock = new Object(); @ArkInject private EmbeddedServerService embeddedServerService; @ArkInject private BizManagerService bizManagerService; private File baseDirectory; private String protocol = DEFAULT_PROTOCOL; private int backgroundProcessorDelay; @Override public WebServer getWebServer(ServletContextInitializer... initializers) { if (embeddedServerService == null && ArkClient.getInjectionService() != null) { // 非应用上下文 (例如: Spring Management Context) 没有经历 Start 生命周期, 不会被注入 ArkServiceInjectProcessor, // 因此 @ArkInject 没有被处理, 需要手动处理 ArkClient.getInjectionService().inject(this); } if (embeddedServerService == null) { // 原有的逻辑中也有这个空值判断, 不确定注入后是否还会有用例会导致 embeddedServerService 为空 // 因此仍保留此 if 空值判断 return super.getWebServer(initializers); } if (embeddedServerService.getEmbedServer(getPort()) == null) { synchronized (lock) { if (embeddedServerService.getEmbedServer(getPort()) == null) { embeddedServerService.putEmbedServer(getPort(), initEmbedTomcat()); } } } Tomcat embedTomcat = (Tomcat) embeddedServerService.getEmbedServer(getPort()); prepareContext(embedTomcat.getHost(), initializers); return getWebServer(embedTomcat); } @Override public String getContextPath() { String contextPath = super.getContextPath(); if (bizManagerService == null) { return contextPath; } Biz biz = bizManagerService.getBizByClassLoader(Thread.currentThread() .getContextClassLoader()); if (!StringUtils.isEmpty(contextPath)) { return contextPath; } else if (biz != null) { if (StringUtils.isEmpty(biz.getWebContextPath())) { return ROOT_WEB_CONTEXT_PATH; } return biz.getWebContextPath(); } else { return ROOT_WEB_CONTEXT_PATH; } } private Tomcat initEmbedTomcat() { Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : getAdditionalTomcatConnectors()) { tomcat.getService().addConnector(additionalConnector); } return tomcat; } @Override public void setBaseDirectory(File baseDirectory) { this.baseDirectory = baseDirectory; } /** * The Tomcat protocol to use when create the {@link Connector}. * * @param protocol the protocol * @see Connector#Connector(String) */ @Override public void setProtocol(String protocol) { AssertUtils.isFalse(StringUtils.isEmpty(protocol), "Protocol must not be empty"); this.protocol = protocol; } @Override public void setBackgroundProcessorDelay(int delay) { this.backgroundProcessorDelay = delay; } private void configureEngine(Engine engine) { engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay); for (Valve valve : getEngineValves()) { engine.getPipeline().addValve(valve); } } @Override protected void postProcessContext(Context context) { ((WebappLoader) context.getLoader()) .setLoaderClass("com.alipay.sofa.ark.web.embed.tomcat.ArkTomcatEmbeddedWebappClassLoader"); } @Override protected void prepareContext(Host host, ServletContextInitializer[] initializers) { if (host.getState() == LifecycleState.NEW) { super.prepareContext(host, initializers); } else { File documentRoot = getValidDocumentRoot(); StandardContext context = new StandardContext(); if (documentRoot != null) { context.setResources(new StandardRoot(context)); } context.setName(getContextPath()); context.setDisplayName(getDisplayName()); context.setPath(getContextPath()); File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase"); context.setDocBase(docBase.getAbsolutePath()); context.addLifecycleListener(new Tomcat.FixContextListener()); context.setParentClassLoader(Thread.currentThread().getContextClassLoader()); resetDefaultLocaleMapping(context); addLocaleMappings(context); context.setUseRelativeRedirects(false); configureTldSkipPatterns(context); WebappLoader loader = new WebappLoader(); loader .setLoaderClass("com.alipay.sofa.ark.web.embed.tomcat.ArkTomcatEmbeddedWebappClassLoader"); loader.setDelegate(true); context.setLoader(loader); if (isRegisterDefaultServlet()) { addDefaultServlet(context); } if (shouldRegisterJspServlet()) { addJspServlet(context); addJasperInitializer(context); } context.addLifecycleListener(new StaticResourceConfigurer(context)); ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); context.setParent(host); configureContext(context, initializersToUse); host.addChild(context); } } /** * Override Tomcat's default locale mappings to align with other servers. See * {@code org.apache.catalina.util.CharsetMapperDefault.properties}. * * @param context the context to reset */ private void resetDefaultLocaleMapping(StandardContext context) { context.addLocaleEncodingMappingParameter(Locale.ENGLISH.toString(), DEFAULT_CHARSET.displayName()); context.addLocaleEncodingMappingParameter(Locale.FRENCH.toString(), DEFAULT_CHARSET.displayName()); } private void addLocaleMappings(StandardContext context) { for (Map.Entry entry : getLocaleCharsetMappings().entrySet()) { context.addLocaleEncodingMappingParameter(entry.getKey().toString(), entry.getValue() .toString()); } } private void configureTldSkipPatterns(StandardContext context) { StandardJarScanFilter filter = new StandardJarScanFilter(); filter.setTldSkip(StringUtils.collectionToCommaDelimitedString(getTldSkipPatterns())); context.getJarScanner().setJarScanFilter(filter); } private void addDefaultServlet(Context context) { Wrapper defaultServlet = context.createWrapper(); defaultServlet.setName("default"); defaultServlet.setServletClass("org.apache.catalina.servlets.DefaultServlet"); defaultServlet.addInitParameter("debug", "0"); defaultServlet.addInitParameter("listings", "false"); defaultServlet.setLoadOnStartup(1); // Otherwise the default location of a Spring DispatcherServlet cannot be set defaultServlet.setOverridable(true); context.addChild(defaultServlet); context.addServletMappingDecoded("/", "default"); } private void addJspServlet(Context context) { Wrapper jspServlet = context.createWrapper(); jspServlet.setName("jsp"); jspServlet.setServletClass(getJsp().getClassName()); jspServlet.addInitParameter("fork", "false"); for (Map.Entry entry : getJsp().getInitParameters().entrySet()) { jspServlet.addInitParameter(entry.getKey(), entry.getValue()); } jspServlet.setLoadOnStartup(3); context.addChild(jspServlet); context.addServletMappingDecoded("*.jsp", "jsp"); context.addServletMappingDecoded("*.jspx", "jsp"); } private void addJasperInitializer(StandardContext context) { try { ServletContainerInitializer initializer = (ServletContainerInitializer) ClassUtils .forName("org.apache.jasper.servlet.JasperInitializer", null).newInstance(); context.addServletContainerInitializer(initializer, null); } catch (Exception ex) { // Probably not Tomcat 8 } } final class StaticResourceConfigurer implements LifecycleListener { private final Context context; private StaticResourceConfigurer(Context context) { this.context = context; } @Override public void lifecycleEvent(LifecycleEvent event) { if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { addResourceJars(getUrlsOfJarsWithMetaInfResources()); } } private void addResourceJars(List resourceJarUrls) { for (URL url : resourceJarUrls) { String path = url.getPath(); if (path.endsWith(".jar") || path.endsWith(".jar!/")) { String jar = url.toString(); if (!jar.startsWith("jar:")) { // A jar file in the file system. Convert to Jar URL. jar = "jar:" + jar + "!/"; } addResourceSet(jar); } else { addResourceSet(url.toString()); } } } private void addResourceSet(String resource) { try { if (isInsideNestedJar(resource)) { // It's a nested jar but we now don't want the suffix because Tomcat // is going to try and locate it as a root URL (not the resource // inside it) resource = resource.substring(0, resource.length() - 2); } URL url = new URL(resource); String path = "/META-INF/resources"; this.context.getResources().createWebResourceSet( WebResourceRoot.ResourceSetType.RESOURCE_JAR, "/", url, path); } catch (Exception ex) { // Ignore (probably not a directory) } } private boolean isInsideNestedJar(String dir) { return dir.indexOf("!/") < dir.lastIndexOf("!/"); } } /** * Factory method called to create the {@link TomcatWebServer}. Subclasses can * override this method to return a different {@link TomcatWebServer} or apply * additional processing to the Tomcat server. * * @param tomcat the Tomcat server. * @return a new {@link TomcatWebServer} instance */ protected WebServer getWebServer(Tomcat tomcat) { return new ArkTomcatWebServer(tomcat, getPort() >= 0, tomcat); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/java/com/alipay/sofa/ark/springboot/web/ArkTomcatWebServer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.web; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import javax.naming.NamingException; import org.apache.catalina.Container; import org.apache.catalina.Context; import org.apache.catalina.Engine; import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; import org.apache.catalina.Service; import org.apache.catalina.connector.Connector; import org.apache.catalina.startup.Tomcat; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.naming.ContextBindings; import org.springframework.boot.web.embedded.tomcat.ConnectorStartFailedException; import org.springframework.boot.web.embedded.tomcat.TomcatWebServer; import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServerException; import org.springframework.util.Assert; /** * NOTE: Tomcat instance will start immediately when create ArkTomcatWebServer object. * * @author Brian Clozel * @author Kristine Jetzke * @author 0.6.0 * @since 2.0.0 */ public class ArkTomcatWebServer implements WebServer { private static final Log logger = LogFactory .getLog(TomcatWebServer.class); private static final AtomicInteger containerCounter = new AtomicInteger(-1); private final Object monitor = new Object(); private final Map serviceConnectors = new HashMap<>(); private final Tomcat tomcat; private final boolean autoStart; private volatile boolean started; private Thread awaitThread; private Tomcat arkEmbedTomcat; /** * Create a new {@link ArkTomcatWebServer} instance. * @param tomcat the underlying Tomcat server */ public ArkTomcatWebServer(Tomcat tomcat) { this(tomcat, true); } /** * Create a new {@link TomcatWebServer} instance. * @param tomcat the underlying Tomcat server * @param autoStart if the server should be started */ public ArkTomcatWebServer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; initialize(); } public ArkTomcatWebServer(Tomcat tomcat, boolean autoStart, Tomcat arkEmbedTomcat) { this(tomcat, autoStart); this.arkEmbedTomcat = arkEmbedTomcat; } private void initialize() throws WebServerException { logger.info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { addInstanceIdToEngineName(); Context context = findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { // Remove service connectors so that protocol binding doesn't // happen when the service is started. removeServiceConnectors(); } }); // Start the server to trigger initialization listeners this.tomcat.start(); // We can re-throw failure exception directly in the main thread rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), Thread.currentThread().getContextClassLoader()); } catch (NamingException ex) { // Naming is not enabled. Continue } // Unlike Jetty, all Tomcat threads are daemon threads. We create a // blocking non-daemon to stop immediate shutdown startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } } private Context findContext() { for (Container child : this.tomcat.getHost().findChildren()) { if (child instanceof Context) { if (child.getParentClassLoader().equals( Thread.currentThread().getContextClassLoader())) { return (Context) child; } } } throw new IllegalStateException("The host does not contain a Context"); } private void addInstanceIdToEngineName() { int instanceId = containerCounter.incrementAndGet(); if (instanceId > 0) { // We already have a tomcat container, so just return the existing tomcat. Engine engine = this.tomcat.getEngine(); engine.setName(engine.getName() + "-" + instanceId); } } private void removeServiceConnectors() { for (Service service : this.tomcat.getServer().findServices()) { Connector[] connectors = service.findConnectors().clone(); this.serviceConnectors.put(service, connectors); for (Connector connector : connectors) { service.removeConnector(connector); } } } private void rethrowDeferredStartupExceptions() throws Exception { Container[] children = this.tomcat.getHost().findChildren(); for (Container container : children) { // just to check current biz status if (container.getParentClassLoader() == Thread.currentThread().getContextClassLoader()) { if (!LifecycleState.STARTED.equals(container.getState())) { throw new IllegalStateException(container + " failed to start"); } } } } private void startDaemonAwaitThread() { awaitThread = new Thread("container-" + (containerCounter.get())) { @Override public void run() { getTomcat().getServer().await(); } }; awaitThread.setContextClassLoader(Thread.currentThread().getContextClassLoader()); awaitThread.setDaemon(false); awaitThread.start(); } @Override public void start() throws WebServerException { synchronized (this.monitor) { if (this.started) { return; } Context context = findContext(); try { addPreviouslyRemovedConnectors(); this.tomcat.getConnector(); checkThatConnectorsHaveStarted(); this.started = true; logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '" + getContextPath() + "'"); } catch (ConnectorStartFailedException ex) { stopSilently(); throw ex; } catch (Exception ex) { throw new WebServerException("Unable to start embedded Tomcat server", ex); } finally { ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass() .getClassLoader()); } } } void checkThatConnectorsHaveStarted() { checkConnectorHasStarted(this.tomcat.getConnector()); for (Connector connector : this.tomcat.getService().findConnectors()) { checkConnectorHasStarted(connector); } } private void checkConnectorHasStarted(Connector connector) { if (LifecycleState.FAILED.equals(connector.getState())) { throw new ConnectorStartFailedException(connector.getPort()); } } public void stopSilently() { stopContext(); try { stopTomcatIfNecessary(); } catch (LifecycleException ex) { // Ignore } } private void stopContext() { Context context = findContext(); getTomcat().getHost().removeChild(context); } private void stopTomcatIfNecessary() throws LifecycleException { if (tomcat != arkEmbedTomcat) { tomcat.destroy(); } awaitThread.stop(); } void addPreviouslyRemovedConnectors() { Service[] services = this.tomcat.getServer().findServices(); for (Service service : services) { Connector[] connectors = this.serviceConnectors.get(service); if (connectors != null) { for (Connector connector : connectors) { service.addConnector(connector); if (!this.autoStart) { stopProtocolHandler(connector); } } this.serviceConnectors.remove(service); } } } private void stopProtocolHandler(Connector connector) { try { connector.getProtocolHandler().stop(); } catch (Exception ex) { logger.error("Cannot pause connector: ", ex); } } Map getServiceConnectors() { return this.serviceConnectors; } @Override public void stop() throws WebServerException { synchronized (this.monitor) { boolean wasStarted = this.started; try { this.started = false; try { stopContext(); stopTomcatIfNecessary(); } catch (Throwable ex) { // swallow and continue } } catch (Exception ex) { throw new WebServerException("Unable to stop embedded Tomcat", ex); } finally { if (wasStarted) { containerCounter.decrementAndGet(); } } } } private String getPortsDescription(boolean localPort) { StringBuilder ports = new StringBuilder(); for (Connector connector : this.tomcat.getService().findConnectors()) { if (ports.length() != 0) { ports.append(' '); } int port = localPort ? connector.getLocalPort() : connector.getPort(); ports.append(port).append(" (").append(connector.getScheme()).append(')'); } return ports.toString(); } @Override public int getPort() { Connector connector = this.tomcat.getConnector(); if (connector != null) { return connector.getLocalPort(); } return 0; } private String getContextPath() { return findContext().getPath(); } /** * Returns access to the underlying Tomcat server. * @return the Tomcat server */ public Tomcat getTomcat() { return this.tomcat; } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.alipay.sofa.ark.springboot.ArkServletAutoConfiguration com.alipay.sofa.ark.springboot.ArkServletLegacyAutoConfiguration com.alipay.sofa.ark.springboot.ArkReactiveAutoConfiguration com.alipay.sofa.ark.springboot.ArkAutoProcessorConfiguration ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/main/resources/META-INF/spring.factories ================================================ org.springframework.context.ApplicationListener=\ com.alipay.sofa.ark.springboot.listener.ArkApplicationStartListener,\ com.alipay.sofa.ark.springboot.listener.ArkDeployStaticBizListener,\ com.alipay.sofa.ark.springboot.listener.PropertiesResetListener org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.alipay.sofa.ark.springboot.ArkServletAutoConfiguration,\ com.alipay.sofa.ark.springboot.ArkServletLegacyAutoConfiguration,\ com.alipay.sofa.ark.springboot.ArkReactiveAutoConfiguration,\ com.alipay.sofa.ark.springboot.ArkAutoProcessorConfiguration ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/java/com/alipay/sofa/ark/springboot/listener/ArkDeployStaticBizListenerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.listener; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.support.startup.EmbedSofaArkBootstrap; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.ContextStartedEvent; import static org.mockito.Mockito.*; /** * @author gaowh * @version 1.0 * @time 2024/12/30 */ public class ArkDeployStaticBizListenerTest { /** * classloader不匹配的场景 */ @Test public void testDiffClassLoader() { ArkDeployStaticBizListener listener = new ArkDeployStaticBizListener(); ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); try (MockedStatic bootstrap = mockStatic(EmbedSofaArkBootstrap.class); MockedStatic arkConfigs = mockStatic(ArkConfigs.class)) { arkConfigs.when(ArkConfigs::isEmbedEnable).thenReturn(true); arkConfigs.when(ArkConfigs::isEmbedStaticBizEnable).thenReturn(true); Thread.currentThread().setContextClassLoader(new ClassLoader() { }); listener.onApplicationEvent(new ContextRefreshedEvent(new AnnotationConfigApplicationContext())); bootstrap.verify(Mockito.times(0), EmbedSofaArkBootstrap::deployStaticBizAfterEmbedMasterBizStarted); } finally { Thread.currentThread().setContextClassLoader(contextClassLoader); } } /** * applicationEvent 不是 ContextRefreshedEvent 的场景 */ @Test public void testNonContextRefreshedEvent() { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); try (MockedStatic bootstrap = mockStatic(EmbedSofaArkBootstrap.class); MockedStatic arkConfigs = mockStatic(ArkConfigs.class)) { ClassLoader classLoader = ArkDeployStaticBizListener.class.getClassLoader(); Thread.currentThread().setContextClassLoader(classLoader); arkConfigs.when(ArkConfigs::isEmbedEnable).thenReturn(true); arkConfigs.when(ArkConfigs::isEmbedStaticBizEnable).thenReturn(true); ArkDeployStaticBizListener listener = new ArkDeployStaticBizListener(); listener.onApplicationEvent(new ContextStartedEvent(new AnnotationConfigApplicationContext())); bootstrap.verify(Mockito.times(0), EmbedSofaArkBootstrap::deployStaticBizAfterEmbedMasterBizStarted); } finally { Thread.currentThread().setContextClassLoader(contextClassLoader); } } /** * applicationEvent不是 spring 根上下文的场景 */ @Test public void testNonSpringRootEvent() { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); try (MockedStatic bootstrap = mockStatic(EmbedSofaArkBootstrap.class); MockedStatic arkConfigs = mockStatic(ArkConfigs.class)) { ClassLoader classLoader = ArkDeployStaticBizListener.class.getClassLoader(); Thread.currentThread().setContextClassLoader(classLoader); arkConfigs.when(ArkConfigs::isEmbedEnable).thenReturn(true); arkConfigs.when(ArkConfigs::isEmbedStaticBizEnable).thenReturn(true); ArkDeployStaticBizListener listener = new ArkDeployStaticBizListener(); listener.onApplicationEvent(new ContextStartedEvent(new AnnotationConfigServletWebServerApplicationContext())); bootstrap.verify(Mockito.times(0), EmbedSofaArkBootstrap::deployStaticBizAfterEmbedMasterBizStarted); } finally { Thread.currentThread().setContextClassLoader(contextClassLoader); } } /** * 事件重复发送的场景 */ @Test public void testDeployed() { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); try (MockedStatic bootstrap = mockStatic(EmbedSofaArkBootstrap.class); MockedStatic arkConfigs = mockStatic(ArkConfigs.class)) { ClassLoader classLoader = ArkDeployStaticBizListener.class.getClassLoader(); Thread.currentThread().setContextClassLoader(classLoader); arkConfigs.when(ArkConfigs::isEmbedEnable).thenReturn(true); arkConfigs.when(ArkConfigs::isEmbedStaticBizEnable).thenReturn(true); ArkDeployStaticBizListener listener = new ArkDeployStaticBizListener(); listener.onApplicationEvent(new ContextRefreshedEvent(new AnnotationConfigApplicationContext())); // 容器刷新事件已经发送过,重复发送不会重复部署 listener.onApplicationEvent(new ContextRefreshedEvent(new AnnotationConfigApplicationContext())); bootstrap.verify(Mockito.times(1), EmbedSofaArkBootstrap::deployStaticBizAfterEmbedMasterBizStarted); } finally { Thread.currentThread().setContextClassLoader(contextClassLoader); } } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/java/com/alipay/sofa/ark/springboot/loader/CachedLaunchedURLClassLoaderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.loader; import org.junit.Before; import org.junit.Test; import org.springframework.boot.loader.archive.ExplodedArchive; import java.io.File; import java.lang.reflect.Field; import java.net.URL; import java.util.Map; import static com.alipay.sofa.ark.springboot.loader.JarLauncher.main; import static org.junit.Assert.*; public class CachedLaunchedURLClassLoaderTest { private CachedLaunchedURLClassLoader cachedLaunchedURLClassLoader; private File resourcesDir = new File("src/test/resources/"); @Before public void setUp() throws Exception { cachedLaunchedURLClassLoader = new CachedLaunchedURLClassLoader(true, new ExplodedArchive( resourcesDir), new URL[] { new URL("file:///" + resourcesDir.getAbsolutePath()) }, this .getClass().getClassLoader()); } @Test public void testLoadClass() throws Exception { Field field = CachedLaunchedURLClassLoader.class.getDeclaredField("classCache"); field.setAccessible(true); assertEquals(0, ((Map) field.get(cachedLaunchedURLClassLoader)).size()); try { cachedLaunchedURLClassLoader.loadClass("a", true); assertTrue(false); } catch (ClassNotFoundException cnfe) { } try { cachedLaunchedURLClassLoader.loadClass("a", true); assertTrue(false); } catch (ClassNotFoundException cnfe) { } assertEquals(CachedLaunchedURLClassLoaderTest.class, cachedLaunchedURLClassLoader.loadClass( "com.alipay.sofa.ark.springboot.loader.CachedLaunchedURLClassLoaderTest", true)); assertEquals(CachedLaunchedURLClassLoaderTest.class, cachedLaunchedURLClassLoader.loadClass( "com.alipay.sofa.ark.springboot.loader.CachedLaunchedURLClassLoaderTest", true)); assertEquals(1, ((Map) field.get(cachedLaunchedURLClassLoader)).size()); } @Test public void testFindResource() throws Exception { Field field = CachedLaunchedURLClassLoader.class.getDeclaredField("resourceUrlCache"); field.setAccessible(true); assertEquals(0, ((Map) field.get(cachedLaunchedURLClassLoader)).size()); assertEquals(null, cachedLaunchedURLClassLoader.findResource("c")); assertEquals(null, cachedLaunchedURLClassLoader.findResource("c")); assertEquals(null, cachedLaunchedURLClassLoader.findResource("d")); assertEquals(2, ((Map) field.get(cachedLaunchedURLClassLoader)).size()); } @Test public void testFindResources() throws Exception { Field field = CachedLaunchedURLClassLoader.class.getDeclaredField("resourcesUrlCache"); field.setAccessible(true); assertEquals(0, ((Map) field.get(cachedLaunchedURLClassLoader)).size()); assertEquals(false, cachedLaunchedURLClassLoader.findResources("b").hasMoreElements()); assertEquals(null, cachedLaunchedURLClassLoader.findResources("b")); assertEquals(1, ((Map) field.get(cachedLaunchedURLClassLoader)).size()); } @Test public void testJarLauncher() throws Exception { try { main(new String[] {}); } catch (Exception e) { } assertNotNull(new JarLauncher().createClassLoader(new URL[] {})); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/java/com/alipay/sofa/ark/springboot/web/ArkTomcatServletWebServerFactoryTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.web; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.service.biz.BizManagerServiceImpl; import com.alipay.sofa.ark.spi.service.injection.InjectionService; import com.alipay.sofa.ark.springboot.web.ArkTomcatServletWebServerFactory.StaticResourceConfigurer; import org.apache.catalina.Context; import org.apache.catalina.LifecycleException; import org.apache.catalina.core.StandardContext; import org.apache.catalina.core.StandardHost; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.boot.web.embedded.tomcat.TomcatWebServer; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.boot.web.servlet.server.Jsp; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.List; import static com.alipay.sofa.ark.spi.constant.Constants.ROOT_WEB_CONTEXT_PATH; import static com.alipay.sofa.ark.spi.model.BizState.RESOLVED; import static java.lang.Thread.currentThread; import static org.junit.Assert.assertEquals; public class ArkTomcatServletWebServerFactoryTest { private ArkTomcatServletWebServerFactory arkTomcatServletWebServerFactory = new ArkTomcatServletWebServerFactory(); private ClassLoader currentThreadContextClassLoader; private InjectionService injectionService; @Before public void setUp() { injectionService = ArkClient.getInjectionService(); ArkClient.setInjectionService(null); currentThreadContextClassLoader = currentThread().getContextClassLoader(); } @After public void tearDown() { ArkClient.setInjectionService(injectionService); currentThread().setContextClassLoader(currentThreadContextClassLoader); } @Test public void testGetWebServerWithEmbeddedServerServiceNull() { assertEquals(TomcatWebServer.class, arkTomcatServletWebServerFactory.getWebServer() .getClass()); } @Test public void testGetContextPath() throws Exception { assertEquals("", arkTomcatServletWebServerFactory.getContextPath()); BizManagerServiceImpl bizManagerService = new BizManagerServiceImpl(); Field field = ArkTomcatServletWebServerFactory.class.getDeclaredField("bizManagerService"); field.setAccessible(true); field.set(arkTomcatServletWebServerFactory, bizManagerService); assertEquals(ROOT_WEB_CONTEXT_PATH, arkTomcatServletWebServerFactory.getContextPath()); BizModel biz = new BizModel(); biz.setBizName("bbb"); biz.setBizState(RESOLVED); biz.setBizVersion("ccc"); biz.setClassLoader(this.getClass().getClassLoader()); bizManagerService.registerBiz(biz); assertEquals(ROOT_WEB_CONTEXT_PATH, arkTomcatServletWebServerFactory.getContextPath()); biz.setWebContextPath("/ddd"); currentThread().setContextClassLoader(biz.getBizClassLoader()); assertEquals("/ddd", arkTomcatServletWebServerFactory.getContextPath()); arkTomcatServletWebServerFactory.setContextPath("/aaa"); assertEquals("/aaa", arkTomcatServletWebServerFactory.getContextPath()); } @Test public void testPrepareContext() throws LifecycleException { StandardHost host = new StandardHost(); host.init(); assertEquals(0, host.getChildren().length); arkTomcatServletWebServerFactory.setRegisterDefaultServlet(true); currentThread().setContextClassLoader(this.getClass().getClassLoader()); Jsp jsp = new Jsp(); jsp.setRegistered(true); // Otherwise JSP won't be loaded by ApplicationClassLoader, so JSP-relative initialization code won't be executed. jsp.setClassName("com.alipay.sofa.ark.springboot.web.ArkTomcatServletWebServerFactoryTest"); arkTomcatServletWebServerFactory.setJsp(jsp); arkTomcatServletWebServerFactory.prepareContext(host, new ServletContextInitializer[] {}); assertEquals(1, host.getChildren().length); } @Test public void testOtherMethods() { arkTomcatServletWebServerFactory.setBackgroundProcessorDelay(10); arkTomcatServletWebServerFactory.setBaseDirectory(null); arkTomcatServletWebServerFactory.setProtocol("8888"); } @Test public void testStaticResourceConfigurer() throws Exception { List urls = new ArrayList<>(); urls.add(new URL("file:///aaa.jar!/")); urls.add(new URL("jar:file:///aaa.jar!/")); urls.add(new URL("file:///aaa")); urls.add(new URL("file:///!/aaa!/")); Constructor declaredConstructor = StaticResourceConfigurer.class .getDeclaredConstructor(ArkTomcatServletWebServerFactory.class, Context.class); declaredConstructor.setAccessible(true); StaticResourceConfigurer staticResourceConfigurer = declaredConstructor.newInstance( arkTomcatServletWebServerFactory, new StandardContext()); Method addResourceJars = StaticResourceConfigurer.class.getDeclaredMethod( "addResourceJars", List.class); addResourceJars.setAccessible(true); addResourceJars.invoke(staticResourceConfigurer, urls); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/java/com/alipay/sofa/ark/springboot/web/ArkTomcatWebServerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.springboot.web; import com.alipay.sofa.ark.web.embed.tomcat.EmbeddedServerServiceImpl; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Field; public class ArkTomcatWebServerTest { private ArkTomcatServletWebServerFactory arkTomcatServletWebServerFactory; private ArkTomcatWebServer arkTomcatWebServer; @Before public void setUp() throws Exception { arkTomcatServletWebServerFactory = new ArkTomcatServletWebServerFactory(); Field field = ArkTomcatServletWebServerFactory.class .getDeclaredField("embeddedServerService"); field.setAccessible(true); field.set(arkTomcatServletWebServerFactory, new EmbeddedServerServiceImpl()); arkTomcatWebServer = (ArkTomcatWebServer) arkTomcatServletWebServerFactory.getWebServer(); } @After public void tearDown() { } @Test public void testGetWebServerWithEmbeddedServerServiceNull() { // NOTE: tomcat can not be stopped and restarted due to a Spring context destroy problem. // Spring community will fix this issue in the future, so catch all exception now. try { arkTomcatWebServer.stopSilently(); } catch (Exception e) { } try { arkTomcatWebServer.stop(); } catch (Exception e) { } } @Test public void testOtherMethods() { arkTomcatWebServer.getPort(); try { arkTomcatWebServer.checkThatConnectorsHaveStarted(); } catch (Exception e) { } try { arkTomcatWebServer.addPreviouslyRemovedConnectors(); } catch (Exception e) { } } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/java/com/alipay/sofa/ark/test/ArkBootRunnerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.test; import com.alipay.sofa.ark.container.test.TestClassLoader; import com.alipay.sofa.ark.spi.event.ArkEvent; import com.alipay.sofa.ark.spi.service.ArkInject; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import com.alipay.sofa.ark.springboot.runner.ArkBootEmbedRunner; import com.alipay.sofa.ark.test.springboot.BaseSpringApplication; import com.alipay.sofa.ark.test.springboot.facade.SampleService; import org.junit.Test; import org.junit.runner.Description; import org.junit.runner.RunWith; import org.junit.runner.manipulation.Filter; import org.junit.runner.manipulation.NoTestsRemainException; import org.junit.runner.manipulation.Sorter; import org.junit.runners.BlockJUnit4ClassRunner; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.lang.reflect.Field; import java.util.Comparator; import static com.alipay.sofa.ark.test.springboot.TestValueHolder.getTestValue; import static org.junit.Assert.*; import static org.springframework.util.ReflectionUtils.*; /** * @author qilong.zql * @since 0.1.0 */ @RunWith(ArkBootEmbedRunner.class) @SpringBootTest(classes = BaseSpringApplication.class) public class ArkBootRunnerTest { @Autowired public SampleService sampleService; @ArkInject public PluginManagerService pluginManagerService; @ArkInject public EventAdminService eventAdminService; @Test public void test() throws NoTestsRemainException { assertNotNull(sampleService); assertNotNull(pluginManagerService); assertEquals("SampleService", sampleService.say()); ArkBootEmbedRunner runner = new ArkBootEmbedRunner(ArkBootRunnerTest.class); Field field = findField(ArkBootEmbedRunner.class, "runner"); assertNotNull(field); makeAccessible(field); BlockJUnit4ClassRunner springRunner = (BlockJUnit4ClassRunner) getField(field, runner); assertTrue(springRunner.getClass().getCanonicalName() .equals(SpringRunner.class.getCanonicalName())); ClassLoader loader = springRunner.getTestClass().getJavaClass().getClassLoader(); assertTrue(loader.getClass().getCanonicalName() .equals(TestClassLoader.class.getCanonicalName())); assertEquals(0, getTestValue()); eventAdminService.sendEvent(new ArkEvent() { @Override public String getTopic() { return "test-event-A"; } }); assertEquals(10, getTestValue()); eventAdminService.sendEvent(new ArkEvent() { @Override public String getTopic() { return "test-event-B"; } }); assertEquals(20, getTestValue()); runner.filter(new Filter() { @Override public boolean shouldRun(Description description) { return true; } @Override public String describe() { return ""; } }); runner.sort(new Sorter(new Comparator() { @Override public int compare(Description o1, Description o2) { return 0; } }) { }); } /** * issue#234 */ @Test public void testLogClassCastBug() { Throwable throwable = null; try { this.getClass().getClassLoader() .loadClass("org.apache.logging.slf4j.Log4jLoggerFactory").newInstance(); } catch (Throwable t) { throwable = t; } assertNull(throwable); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/java/com/alipay/sofa/ark/test/ArkBootTestNGTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.test; import com.alipay.sofa.ark.support.listener.TestNGOnArkEmbeded; import com.alipay.sofa.ark.test.springboot.BaseSpringApplication; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; import org.testng.annotations.Test; /** * @author qilong.zql * @since 1.0.0 */ @TestNGOnArkEmbeded @SpringBootTest(classes = BaseSpringApplication.class) public class ArkBootTestNGTest extends AbstractTestNGSpringContextTests { @Test public void testSpringTestNG() { // Ignore } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/java/com/alipay/sofa/ark/test/MultiArkBootRunnerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.test; import com.alipay.sofa.ark.springboot.runner.ArkBootEmbedRunner; import com.alipay.sofa.ark.springboot.runner.ArkBootRunner; import com.alipay.sofa.ark.test.springboot.BaseSpringApplication; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.ApplicationContext; /** * @author qilong.zql * @since 1.0.0 */ @RunWith(ArkBootEmbedRunner.class) @SpringBootTest(classes = BaseSpringApplication.class) public class MultiArkBootRunnerTest { @MockBean private ApplicationContext applicationContext; @Test public void test() { // just to test issue 252 } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/java/com/alipay/sofa/ark/test/SpringbootRunnerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.test; import com.alipay.sofa.ark.spi.service.ArkInject; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; import com.alipay.sofa.ark.test.springboot.facade.SampleService; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import static com.alipay.sofa.ark.api.ArkClient.getInjectionService; import static com.alipay.sofa.ark.common.util.ClassLoaderUtils.pushContextClassLoader; import static com.alipay.sofa.ark.spi.constant.Constants.EMBED_ENABLE; import static com.alipay.sofa.ark.test.springboot.BaseSpringApplication.main; import static com.alipay.sofa.ark.test.springboot.TestValueHolder.getTestValue; import static java.lang.ClassLoader.getSystemClassLoader; import static java.lang.System.setProperty; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; /** * @author bingjie.lbj */ public class SpringbootRunnerTest { @Autowired public SampleService sampleService; @ArkInject PluginManagerService pluginManagerService; @ArkInject EventAdminService eventAdminService; @Before public void before() { pushContextClassLoader(getSystemClassLoader()); setProperty(EMBED_ENABLE, "true"); } @After public void after() { setProperty(EMBED_ENABLE, ""); } @Test public void test() { try { main(new String[]{}); getInjectionService().inject(this); assertNotNull(pluginManagerService); assertEquals(0, getTestValue()); eventAdminService.sendEvent(() -> "test-event-A"); assertEquals(10, getTestValue()); eventAdminService.sendEvent(() -> "test-event-B"); assertEquals(20, getTestValue()); } catch (Exception e) { } } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/java/com/alipay/sofa/ark/test/springboot/BaseSpringApplication.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.test.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ImportResource; import static org.springframework.boot.SpringApplication.exit; /** * @author qilong.zql * @since 0.1.0 */ @ImportResource({ "classpath*:META-INF/sofa-ark-test/*.xml" }) @SpringBootApplication public class BaseSpringApplication { private static SpringApplication springApplication; private static ApplicationContext applicationContext; public static void main(String[] args) { springApplication = new SpringApplication(BaseSpringApplication.class); applicationContext = springApplication.run(args); } public static void stop() { exit(applicationContext, () -> 0); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/java/com/alipay/sofa/ark/test/springboot/RegisterMockEmbedTomcatService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.test.springboot; import com.alipay.sofa.ark.container.registry.ContainerServiceProvider; import com.alipay.sofa.ark.spi.service.ArkInject; import com.alipay.sofa.ark.spi.service.registry.RegistryService; import com.alipay.sofa.ark.spi.web.EmbeddedServerService; import com.alipay.sofa.ark.web.embed.tomcat.EmbeddedServerServiceImpl; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; /** * @author qilong.zql * @since 0.6.0 */ @Component public class RegisterMockEmbedTomcatService implements BeanPostProcessor, InitializingBean { @ArkInject private RegistryService registryService; @Override public void afterPropertiesSet() throws Exception { registryService.publishService(EmbeddedServerService.class, new EmbeddedServerServiceImpl(), new ContainerServiceProvider()); } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/java/com/alipay/sofa/ark/test/springboot/TestValueHolder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.test.springboot; /** * @author qilong.zql * @since 0.6.0 */ public class TestValueHolder { private static int testValue = 0; public static int getTestValue() { return testValue; } public static void setTestValue(int testValue) { TestValueHolder.testValue = testValue; } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/java/com/alipay/sofa/ark/test/springboot/facade/SampleService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.test.springboot.facade; /** * @author qilong.zql * @since 0.1.0 */ public interface SampleService { /** * a simple test facade * @return */ String say(); } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/java/com/alipay/sofa/ark/test/springboot/impl/SampleServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.test.springboot.impl; import com.alipay.sofa.ark.test.springboot.facade.SampleService; /** * @author qilong.zql * @since 0.3.0 */ public class SampleServiceImpl implements SampleService { @Override public String say() { return "SampleService"; } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/java/com/alipay/sofa/ark/test/springboot/impl/TestBizEventHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.test.springboot.impl; import com.alipay.sofa.ark.spi.event.ArkEvent; import com.alipay.sofa.ark.spi.service.event.EventHandler; import com.alipay.sofa.ark.test.springboot.TestValueHolder; /** * @author qilong.zql * @since 0.6.0 */ public class TestBizEventHandler implements EventHandler { @Override public void handleEvent(ArkEvent event) { if (event.getTopic().equals("test-event-A")) { TestValueHolder.setTestValue(10); } else if (event.getTopic().equals("test-event-B")) { TestValueHolder.setTestValue(20); } } @Override public int getPriority() { return LOWEST_PRECEDENCE + 20; } } ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/resources/META-INF/sofa-ark-test/sofa-ark-test.xml ================================================ ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/resources/config/application.properties ================================================ management.server.port=8888 spring.application.name=test ================================================ FILE: sofa-ark-parent/support/ark-springboot-integration/ark-springboot-starter/src/test/resources/logback.xml ================================================ %d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger{36}.%M - %msg%n ================================================ FILE: sofa-ark-parent/support/ark-support-starter/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-support-starter` **Package**: `com.alipay.sofa.ark.support` This module provides startup support and test integration for SOFAArk applications. ## Purpose - Bootstrap Ark container in IDE or test environments - Provide TestNG and JUnit runners for testing on Ark - Support embedded Ark execution ## Key Classes ### Startup (`startup/`) - `SofaArkBootstrap` - Bootstrap Ark container for testing/IDE development - `EmbedSofaArkBootstrap` - Embedded mode bootstrap - `EntryMethod` - Entry method execution handler ### Test Runners (`runner/`) - `ArkJUnit4Runner` - JUnit 4 runner for tests on Ark - `ArkJUnit4EmbedRunner` - JUnit 4 runner for embedded Ark - `JUnitExecutionListener` - JUnit execution listener ### TestNG Support (`listener/`) - `TestNGOnArk` - Annotation to run TestNG tests on Ark - `TestNGOnArkEmbeded` - Annotation for embedded mode - `ArkTestNGExecutionListener` - TestNG execution listener - `ArkTestNGInvokedMethodListener` - TestNG method listener - `ArkTestNGAlterSuiteListener` - TestNG suite listener ### Common Utilities (`common/`) - `DelegateArkContainer` - Delegate to Ark container - `DelegateToMasterBizClassLoaderHook` - ClassLoader delegation hook - `AddBizInResourcesHook` - Hook to add biz from resources - `MasterBizEnvironmentHolder` - Hold master biz environment ### Threading (`thread/`) - `IsolatedThreadGroup` - Thread group for isolated execution - `LaunchRunner` - Run launch in isolated thread ## Usage for Testing ### JUnit 4 ```java @RunWith(ArkJUnit4Runner.class) public class MyTest { @Test public void test() { ... } } ``` ### TestNG ```java @TestNGOnArk public class MyTest { @Test public void test() { ... } } ``` ## Dependencies - `sofa-ark-container` - Container implementation - `sofa-ark-spi` - Service interfaces - `junit` / `testng` - Test frameworks ## Used By - Application test code - IDE development mode ================================================ FILE: sofa-ark-parent/support/ark-support-starter/pom.xml ================================================ sofa-ark-support com.alipay.sofa ${sofa.ark.version} 4.0.0 sofa-ark-support-starter ${project.groupId}:${project.artifactId} org.springframework.boot spring-boot com.alipay.sofa sofa-ark-common com.alipay.sofa sofa-ark-spi com.alipay.sofa sofa-ark-archive junit junit provided org.testng testng provided com.alipay.sofa sofa-ark-all ${sofa.ark.version.old} test com.google.inject guice test org.mockito mockito-core test org.mockito mockito-inline test org.apache.maven.plugins maven-surefire-plugin 1 false org.apache.maven.surefire surefire-junit47 ${surefire.version} org.apache.maven.surefire surefire-testng ${surefire.version} ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/common/AddBizInResourcesHook.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.common; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.loader.JarBizArchive; import com.alipay.sofa.ark.loader.archive.JarFileArchive; import com.alipay.sofa.ark.spi.archive.Archive; import com.alipay.sofa.ark.spi.archive.BizArchive; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.service.biz.AddBizToStaticDeployHook; import com.alipay.sofa.ark.spi.service.extension.Extension; import java.io.File; import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.List; import static com.alipay.sofa.ark.spi.constant.Constants.SOFA_ARK_MODULE; /** * @author lianglipeng.llp@alibaba-inc.com * @version $Id: AddBizInResourcesHook.java, v 0.1 2024年07月06日 19:48 立蓬 Exp $ */ @Extension("add-biz-in-resources-to-deploy") public class AddBizInResourcesHook implements AddBizToStaticDeployHook { @Override public List getStaticBizToAdd() throws Exception { List archives = new ArrayList<>(); if (ArkConfigs.isEmbedEnable() && isEmbedStaticBizInResourceEnable()) { archives.addAll(getBizArchiveFromResources()); } return archives; } private boolean isEmbedStaticBizInResourceEnable() { return ArkConfigs.getBooleanValue(Constants.EMBED_STATIC_BIZ_IN_RESOURCE_ENABLE, Boolean.TRUE); } protected List getBizArchiveFromResources() throws Exception { List archives = new ArrayList<>(); URL bizDirURL = ArkClient.getMasterBiz().getBizClassLoader().getResource(SOFA_ARK_MODULE); if (null == bizDirURL) { return archives; } if (bizDirURL.getProtocol().equals("file")) { return getBizArchiveForFile(bizDirURL); } if (bizDirURL.getProtocol().equals("jar")) { return getBizArchiveForJar(bizDirURL); } return archives; } private List getBizArchiveForFile(URL bizDirURL) throws Exception { List archives = new ArrayList<>(); File bizDir = org.apache.commons.io.FileUtils.toFile(bizDirURL); if (!bizDir.exists() || !bizDir.isDirectory() || null == bizDir.listFiles()) { return archives; } for (File bizFile : bizDir.listFiles()) { archives.add(new JarBizArchive(new JarFileArchive(bizFile))); } return archives; } private List getBizArchiveForJar(URL bizDirURL) throws Exception{ List archives = new ArrayList<>(); JarFileArchive jarFileArchive = getJarFileArchiveFromUrl(bizDirURL); String prefix = getEntryName(bizDirURL); List archivesFromJar = jarFileArchive.getNestedArchives(entry -> !entry.isDirectory() && entry.getName().startsWith(prefix) && !entry.getName().equals(prefix)); for (Archive archiveFromJarEntry : archivesFromJar) { archives.add(new JarBizArchive(archiveFromJarEntry)); } return archives; } private JarFileArchive getJarFileArchiveFromUrl(URL url) throws Exception { String jarPath = substringBefore(((JarURLConnection) url.openConnection()).getJarFile() .getName(), "!"); return new JarFileArchive(com.alipay.sofa.ark.common.util.FileUtils.file(jarPath)); } private String getEntryName(URL url) throws IOException { String classPathEntryName = substringAfter(((JarURLConnection) url.openConnection()) .getJarFile().getName(), "!/"); String urlEntryNameFromClassPath = ((JarURLConnection) url.openConnection()).getJarEntry() .getName(); return String.join("/", classPathEntryName, urlEntryNameFromClassPath); } public static String substringBefore(String str, String separator) { if (str != null && separator != null && str.length() != 0) { if (separator.length() == 0) { return ""; } else { int pos = str.indexOf(separator); return pos == -1 ? str : str.substring(0, pos); } } else { return str; } } public static String substringAfter(String str, String separator) { if (str != null && str.length() != 0) { if (separator == null) { return ""; } else { int pos = str.indexOf(separator); return pos == -1 ? "" : str.substring(pos + separator.length()); } } else { return str; } } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/common/DelegateArkContainer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.common; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.support.startup.SofaArkBootstrap; import java.lang.reflect.Method; /** * wrap the {@literal com.alipay.sofa.ark.container.ArkContainer} * * @author qilong.zql * @since 0.1.0 */ public class DelegateArkContainer { private static final String TEST_HELPER = "com.alipay.sofa.ark.container.test.TestHelper"; private static final String CREATE_TEST_CLASSLOADER = "createTestClassLoader"; private static final String STOP_CONTAINER = "stop"; private static Method CREATE_TEST_CLASSLOADER_METHOD; private static Method STOP_CONTAINER_METHOD; private static volatile Object arkContainer; private static Object testHelper; private static volatile ClassLoader testClassLoader; private static final Object LOCK = new Object(); /** * Launch Ark Container when run tests */ public static void launch(Class testClass) { if (arkContainer == null) { synchronized (LOCK) { if (arkContainer == null) { Object container = SofaArkBootstrap.prepareContainerForTest(testClass); wrapping(container); arkContainer = container; } } } ClassLoaderUtils.pushContextClassLoader(DelegateArkContainer.getTestClassLoader()); } /** * wrap {@literal com.alipay.sofa.ark.container.ArkContainer} */ protected static void wrapping(Object container) { AssertUtils.assertNotNull(container, "Ark Container must be not null."); try { Class testHelperClass = container.getClass().getClassLoader().loadClass(TEST_HELPER); testHelper = testHelperClass.getConstructor(Object.class).newInstance(container); CREATE_TEST_CLASSLOADER_METHOD = testHelperClass.getMethod(CREATE_TEST_CLASSLOADER); STOP_CONTAINER_METHOD = container.getClass().getMethod(STOP_CONTAINER); } catch (Exception ex) { // impossible situation throw new RuntimeException(ex); } } /** * Get {@literal com.alipay.sofa.ark.container.test.TestClassLoader}, used by * loading test class * * @return */ public static ClassLoader getTestClassLoader() { if (testClassLoader == null) { synchronized (LOCK) { if (testClassLoader == null) { try { testClassLoader = (ClassLoader) CREATE_TEST_CLASSLOADER_METHOD .invoke(testHelper); } catch (Exception ex) { throw new RuntimeException(ex); } } } } return testClassLoader; } /** * Check whether {@literal com.alipay.sofa.ark.container.ArkContainer} startup or not. */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean isStarted() { return arkContainer != null; } /** * Load class using {@literal com.alipay.sofa.ark.container.test.TestClassLoader} * @param name * @return */ public static Class loadClass(String name) { try { return getTestClassLoader().loadClass(name); } catch (Exception ex) { throw new RuntimeException(); } } public static void shutdown() { if (arkContainer != null) { try { STOP_CONTAINER_METHOD.invoke(arkContainer); arkContainer = null; } catch (Exception ex) { throw new RuntimeException(ex); } } } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/common/DelegateToMasterBizClassLoaderHook.java ================================================ package com.alipay.sofa.ark.support.common; /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderHook; import com.alipay.sofa.ark.spi.service.classloader.ClassLoaderService; import com.alipay.sofa.ark.spi.service.extension.Extension; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.List; /** * A default hook for biz classloader. Trying to post load class by master biz if not found * * @author bingjie.lbj */ @Extension("biz-classloader-hook") public class DelegateToMasterBizClassLoaderHook implements ClassLoaderHook { private static String CGLIB_FLAG = "CGLIB$$"; @Override public Class preFindClass(String name, ClassLoaderService classLoaderService, Biz biz) throws ClassNotFoundException { return null; } @Override public Class postFindClass(String name, ClassLoaderService classLoaderService, Biz biz) throws ClassNotFoundException { ClassLoader masterClassLoader = ArkClient.getMasterBiz().getBizClassLoader(); if (biz == null || (biz.getBizClassLoader() == masterClassLoader)) { return null; } // The cglib proxy class cannot be delegate to the master, it must be created by the biz's own defineClass // see: spring 6, org.springframework.cglib.core.AbstractClassGenerator.generate if (name.contains(CGLIB_FLAG)) { return null; } // if Master Biz contains same class in multi jar, need to check each whether is provided Class clazz = masterClassLoader.loadClass(name); if (clazz != null) { if (biz.isDeclared(clazz.getProtectionDomain().getCodeSource().getLocation(), "")) { return clazz; } try { String classResourceName = name.replace('.', '/') + ".class"; Enumeration urls = masterClassLoader.getResources(classResourceName); while (urls.hasMoreElements()) { URL resourceUrl = urls.nextElement(); if (resourceUrl != null && biz.isDeclared(resourceUrl, classResourceName)) { ArkLoggerFactory.getDefaultLogger().warn( String.format("find class %s from %s in multiple dependencies.", name, resourceUrl.getFile())); return clazz; } } } catch (IOException e) { return null; } } return null; } @Override public URL preFindResource(String name, ClassLoaderService classLoaderService, Biz biz) { return null; } @Override public URL postFindResource(String name, ClassLoaderService classLoaderService, Biz biz) { if (biz == null || (!biz.isDeclaredMode() && shouldSkip(name))) { return null; } ClassLoader masterClassLoader = ArkClient.getMasterBiz().getBizClassLoader(); if (biz.getBizClassLoader() == masterClassLoader) { return null; } try { URL resourceUrl = masterClassLoader.getResource(name); if (resourceUrl != null && biz.isDeclared(resourceUrl, name)) { return resourceUrl; } Enumeration matchResourceUrls = postFindResources(name, classLoaderService, biz); // get the first resource url when match multiple resources if (matchResourceUrls != null && matchResourceUrls.hasMoreElements()) { return matchResourceUrls.nextElement(); } } catch (Exception e) { return null; } return null; } @Override public Enumeration preFindResources(String name, ClassLoaderService classLoaderService, Biz biz) throws IOException { return null; } @Override public Enumeration postFindResources(String name, ClassLoaderService classLoaderService, Biz biz) throws IOException { if (biz == null || (!biz.isDeclaredMode() && shouldSkip(name))) { return null; } ClassLoader masterClassLoader = ArkClient.getMasterBiz().getBizClassLoader(); if (biz.getBizClassLoader() == masterClassLoader) { return null; } try { Enumeration resourceUrls = masterClassLoader.getResources(name); List matchedResourceUrls = new ArrayList<>(); while (resourceUrls.hasMoreElements()) { URL resourceUrl = resourceUrls.nextElement(); if (resourceUrl != null && biz.isDeclared(resourceUrl, name)) { matchedResourceUrls.add(resourceUrl); } } return Collections.enumeration(matchedResourceUrls); } catch (Exception e) { return null; } } private boolean shouldSkip(String resourceName) { return !resourceName.endsWith(".class"); } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/common/MasterBizEnvironmentHolder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.common; import org.springframework.core.env.Environment; /** * @author lianglipeng.llp@alibaba-inc.com * @version $Id: SpringUtil.java, v 0.1 2024年07月09日 19:51 立蓬 Exp $ */ public class MasterBizEnvironmentHolder { private static Environment environment; public static void setEnvironment(Environment environment) { MasterBizEnvironmentHolder.environment = environment; } public static Environment getEnvironment() { if (null == environment) { throw new RuntimeException("master environment is null"); } return environment; } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/listener/ArkTestNGAlterSuiteListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.listener; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.support.common.DelegateArkContainer; import org.testng.IAlterSuiteListener; import org.testng.xml.XmlClass; import org.testng.xml.XmlSuite; import org.testng.xml.XmlTest; import java.util.List; import static com.alipay.sofa.ark.spi.constant.Constants.EMBED_ENABLE; import static com.alipay.sofa.ark.spi.constant.Constants.MASTER_BIZ; /** * @author qilong.zql * @since 0.3.0 */ public class ArkTestNGAlterSuiteListener implements IAlterSuiteListener { @Override public void alter(List suites) { for (XmlSuite xmlSuite : suites) { resetXmlSuite(xmlSuite); resetChildrenXmlSuite(xmlSuite.getChildSuites()); } } protected void resetXmlSuite(XmlSuite suite) { if (suite == null) { return; } resetXmlSuite(suite.getParentSuite()); resetSingleXmlSuite(suite); } protected void resetChildrenXmlSuite(List childSuites) { if (childSuites.isEmpty()) { return; } for (XmlSuite xmlSuite : childSuites) { resetChildrenXmlSuite(xmlSuite.getChildSuites()); resetSingleXmlSuite(xmlSuite); } } protected void resetSingleXmlSuite(XmlSuite suite) { for (XmlTest xmlTest : suite.getTests()) { for (XmlClass xmlClass : xmlTest.getClasses()) { Class testClass = xmlClass.getSupportClass(); if (testClass.getAnnotation(TestNGOnArk.class) != null) { if (!DelegateArkContainer.isStarted()) { DelegateArkContainer.launch(testClass); } try { xmlClass.setClass(DelegateArkContainer.getTestClassLoader().loadClass( testClass.getCanonicalName())); } catch (ClassNotFoundException ex) { throw new ArkRuntimeException(String.format( "Load testNG test class %s failed.", testClass.getCanonicalName()), ex); } } else if (testClass.getAnnotation(TestNGOnArkEmbeded.class) != null) { if (!DelegateArkContainer.isStarted()) { System.setProperty(EMBED_ENABLE, "true"); System.setProperty(MASTER_BIZ, "test master biz"); DelegateArkContainer.launch(testClass); System.clearProperty(EMBED_ENABLE); System.clearProperty(MASTER_BIZ); } try { xmlClass.setClass(DelegateArkContainer.getTestClassLoader().loadClass( testClass.getCanonicalName())); } catch (ClassNotFoundException ex) { throw new ArkRuntimeException(String.format( "Load testNG test class %s failed.", testClass.getCanonicalName()), ex); } } } } } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/listener/ArkTestNGExecutionListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.listener; import com.alipay.sofa.ark.support.common.DelegateArkContainer; import org.testng.IExecutionListener; /** * Shutdown ark container * * @author qilong.zql * @since 0.4.0 */ public class ArkTestNGExecutionListener implements IExecutionListener { @Override public void onExecutionStart() { } @Override public void onExecutionFinish() { DelegateArkContainer.shutdown(); } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/listener/ArkTestNGInvokedMethodListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.listener; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.support.common.DelegateArkContainer; import org.testng.IInvokedMethod; import org.testng.IInvokedMethodListener; import org.testng.ITestResult; import java.lang.annotation.Annotation; /** * @author qilong.zql * @since 0.3.0 */ public class ArkTestNGInvokedMethodListener implements IInvokedMethodListener { @Override public void beforeInvocation(IInvokedMethod method, ITestResult testResult) { Class testClass = method.getTestMethod().getTestClass().getRealClass(); if (isTestOnArk(testClass)) { ClassLoaderUtils.pushContextClassLoader(DelegateArkContainer.getTestClassLoader()); } else { ClassLoaderUtils.pushContextClassLoader(ClassLoader.getSystemClassLoader()); } } @Override public void afterInvocation(IInvokedMethod method, ITestResult testResult) { ClassLoaderUtils.pushContextClassLoader(ClassLoader.getSystemClassLoader()); } protected boolean isTestOnArk(Class testClass) { for (Annotation annotation : testClass.getAnnotations()) { String annotationType = annotation.annotationType().getCanonicalName(); if (annotationType.equals(TestNGOnArk.class.getCanonicalName()) || annotationType.equals(TestNGOnArkEmbeded.class.getCanonicalName())) { return true; } } return false; } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/listener/TestNGOnArk.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.listener; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * any test class annotated by {@link TestNGOnArk} represents * that it would run on ark container * * @author qilong.zql * @since 0.3.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface TestNGOnArk { } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/listener/TestNGOnArkEmbeded.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.listener; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * any test class annotated by {@link TestNGOnArkEmbeded} represents * that it would run on ark container * * used for test ark biz like koupleless, which run ark plugin in embed mode * please refer {@link com.alipay.sofa.ark.spi.service.plugin.PluginFactoryService#createEmbedPlugin(com.alipay.sofa.ark.spi.archive.PluginArchive, java.lang.ClassLoader)} * * @author lvjing2 * @since 2.2.10 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface TestNGOnArkEmbeded { } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/runner/ArkJUnit4EmbedRunner.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.runner; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.support.common.DelegateArkContainer; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.InitializationError; import org.junit.runners.model.TestClass; import static com.alipay.sofa.ark.spi.constant.Constants.EMBED_ENABLE; import static com.alipay.sofa.ark.spi.constant.Constants.MASTER_BIZ; /** * used for test ark biz like koupleless, which run ark plugin in embed mode * please refer {@link com.alipay.sofa.ark.spi.service.plugin.PluginFactoryService#createEmbedPlugin(com.alipay.sofa.ark.spi.archive.PluginArchive, java.lang.ClassLoader)} * * @author lvjing2 * @since 2.2.10 */ public class ArkJUnit4EmbedRunner extends BlockJUnit4ClassRunner { /** * Creates a BlockJUnit4ClassRunner to run {@code klass} * * @param klass * @throws InitializationError if the test class is malformed. */ public ArkJUnit4EmbedRunner(Class klass) throws InitializationError { super(klass); } @Override protected TestClass createTestClass(Class testClass) { try { if (!DelegateArkContainer.isStarted()) { System.setProperty(EMBED_ENABLE, "true"); System.setProperty(MASTER_BIZ, "test master biz"); DelegateArkContainer.launch(testClass); System.clearProperty(EMBED_ENABLE); System.clearProperty(MASTER_BIZ); } ClassLoader testClassLoader = DelegateArkContainer.getTestClassLoader(); TestClass testKlazz = super.createTestClass(testClassLoader.loadClass(testClass .getName())); ClassLoaderUtils.pushContextClassLoader(ClassLoader.getSystemClassLoader()); return testKlazz; } catch (Exception ex) { throw new RuntimeException(ex); } } @Override public void run(RunNotifier notifier) { notifier.addListener(JUnitExecutionListener.getRunListener()); super.run(notifier); } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/runner/ArkJUnit4Runner.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.runner; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.support.common.DelegateArkContainer; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.InitializationError; import org.junit.runners.model.TestClass; /** * @author qilong.zql * @since 0.1.0 */ public class ArkJUnit4Runner extends BlockJUnit4ClassRunner { /** * Creates a BlockJUnit4ClassRunner to run {@code klass} * * @param klass * @throws InitializationError if the test class is malformed. */ public ArkJUnit4Runner(Class klass) throws InitializationError { super(klass); } @Override protected TestClass createTestClass(Class testClass) { try { if (!DelegateArkContainer.isStarted()) { DelegateArkContainer.launch(testClass); } ClassLoader testClassLoader = DelegateArkContainer.getTestClassLoader(); TestClass testKlazz = super.createTestClass(testClassLoader.loadClass(testClass .getName())); ClassLoaderUtils.pushContextClassLoader(ClassLoader.getSystemClassLoader()); return testKlazz; } catch (Exception ex) { throw new RuntimeException(ex); } } @Override public void run(RunNotifier notifier) { notifier.addListener(JUnitExecutionListener.getRunListener()); super.run(notifier); } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/runner/JUnitExecutionListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.runner; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.support.common.DelegateArkContainer; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.RunWith; import org.junit.runner.notification.RunListener; /** * @author qilong.zql * @since 0.3.0 */ public class JUnitExecutionListener extends RunListener { private final static String ARK_JUNIT4_RUNNER = "com.alipay.sofa.ark.support.runner.ArkJUnit4Runner"; private final static String ARK_JUNIT4_EMBED_RUNNER = "com.alipay.sofa.ark.support.runner.ArkJUnit4EmbedRunner"; private final static String ARK_BOOT_RUNNER = "com.alipay.sofa.ark.springboot.runner.ArkBootRunner"; private final static String ARK_BOOT_EMBED_RUNNER = "com.alipay.sofa.ark.springboot.runner.ArkBootEmbedRunner"; private final static Object LOCK = new Object(); private static volatile RunListener singleton; private JUnitExecutionListener() { } @Override public void testStarted(Description description) throws Exception { if (isTestOnArkContainer(description)) { ClassLoaderUtils.pushContextClassLoader(DelegateArkContainer.getTestClassLoader()); } else { ClassLoaderUtils.pushContextClassLoader(ClassLoader.getSystemClassLoader()); } super.testStarted(description); } @Override public void testFinished(Description description) throws Exception { super.testStarted(description); ClassLoaderUtils.pushContextClassLoader(ClassLoader.getSystemClassLoader()); } protected boolean isTestOnArkContainer(Description description) { RunWith runWith = description.getTestClass().getAnnotation(RunWith.class); if (runWith == null) { return false; } Class runnerClass = runWith.value(); String className = runnerClass.getName(); return ARK_JUNIT4_RUNNER.equals(className) || ARK_JUNIT4_EMBED_RUNNER.equals(className) || ARK_BOOT_RUNNER.equals(className) || ARK_BOOT_EMBED_RUNNER.equals(className); } public static RunListener getRunListener() { if (singleton == null) { synchronized (LOCK) { if (singleton == null) { singleton = new JUnitExecutionListener(); } } } return singleton; } @Override public void testRunFinished(Result result) throws Exception { super.testRunFinished(result); DelegateArkContainer.shutdown(); } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/startup/EmbedSofaArkBootstrap.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.startup; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.bootstrap.ClasspathLauncher; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.loader.EmbedClassPathArchive; import com.alipay.sofa.ark.spi.argument.CommandArgument; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.support.common.DelegateToMasterBizClassLoaderHook; import org.springframework.core.env.Environment; import java.lang.reflect.Method; import java.net.URL; import java.util.concurrent.atomic.AtomicBoolean; /** * Launch an embed ark container * * @author bingjie.lbj */ public class EmbedSofaArkBootstrap { private static AtomicBoolean started = new AtomicBoolean(false); static Object arkContainer; public static void launch(Environment environment) { if (started.compareAndSet(false, true)) { EntryMethod entryMethod = new EntryMethod(Thread.currentThread()); getOrSetDefault( Constants.MASTER_BIZ, environment.getProperty(Constants.MASTER_BIZ, environment.getProperty("spring.application.name", "master biz"))); getOrSetDefault(Constants.BIZ_CLASS_LOADER_HOOK_DIR, environment.getProperty(Constants.BIZ_CLASS_LOADER_HOOK_DIR)); getOrSetDefault(Constants.PLUGIN_EXPORT_CLASS_ENABLE, environment.getProperty(Constants.PLUGIN_EXPORT_CLASS_ENABLE, "false")); getOrSetDefault(Constants.BIZ_CLASS_LOADER_HOOK_DIR, DelegateToMasterBizClassLoaderHook.class.getName()); try { URL[] urls = getURLClassPath(); ClasspathLauncher launcher = new ClasspathLauncher(new EmbedClassPathArchive( entryMethod.getDeclaringClassName(), entryMethod.getMethod().getName(), urls)); arkContainer = launcher.launch(new String[] {}, getClasspath(urls), entryMethod.getMethod()); } catch (Throwable e) { throw new RuntimeException(e); } } } private static void getOrSetDefault(String key, String defaultValue) { String value = ArkConfigs.getStringValue(key); if (value == null && defaultValue != null) { ArkConfigs.setSystemProperty(key, defaultValue); } } private static String getClasspath(URL[] urls) { StringBuilder sb = new StringBuilder(); for (URL url : urls) { sb.append(url.toExternalForm()).append(CommandArgument.CLASSPATH_SPLIT); } return sb.toString(); } private static URL[] getURLClassPath() { ClassLoader classLoader = EmbedSofaArkBootstrap.class.getClassLoader(); return ClassLoaderUtils.getURLs(classLoader); } /** * 只会扫描classpath下静态biz包,并启动 */ public static void deployStaticBizAfterEmbedMasterBizStarted() { if (null == arkContainer) { throw new RuntimeException( "ArkContainer is null when deploying biz after embed master biz started."); } try { ClassLoader containerClassLoader = arkContainer.getClass().getClassLoader(); ClassLoader oldClassLoader = ClassLoaderUtils .pushContextClassLoader(containerClassLoader); Method deployBizMethod = arkContainer.getClass().getMethod( "deployBizAfterMasterBizReady"); deployBizMethod.invoke(arkContainer); ClassLoaderUtils.popContextClassLoader(oldClassLoader); } catch (Exception e) { throw new RuntimeException( "Meet exception when deploying biz after embed master biz started!", e); } } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/startup/EntryMethod.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.startup; import com.alipay.sofa.ark.common.util.AssertUtils; import java.lang.reflect.Method; import java.lang.reflect.Modifier; /** * The "main" method located from a running thread. * * @author qilong.zql * @since 0.1.0 * @author Phillip Webb */ public class EntryMethod { private final Method method; public EntryMethod() { this(Thread.currentThread()); } public EntryMethod(Thread thread) { AssertUtils.assertNotNull(thread, "Thread must not be null"); this.method = getMainMethod(thread); } private Method getMainMethod(Thread thread) { for (StackTraceElement element : thread.getStackTrace()) { if ("main".equals(element.getMethodName())) { Method method = getMainMethod(element); if (method != null) { return method; } } } throw new IllegalStateException("Unable to find main method"); } private Method getMainMethod(StackTraceElement element) { try { Class elementClass = Class.forName(element.getClassName()); Method method = elementClass.getDeclaredMethod("main", String[].class); if (Modifier.isStatic(method.getModifiers())) { return method; } } catch (Throwable ex) { // ignore } return null; } /** * Returns the actual main method. * @return the main method */ public Method getMethod() { return this.method; } /** * Return the name of the declaring class. * @return the declaring class name */ public String getDeclaringClassName() { return this.method.getDeclaringClass().getName(); } public String getMethodName() { return this.method.getName(); } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/startup/SofaArkBootstrap.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.startup; import com.alipay.sofa.ark.bootstrap.ClasspathLauncher; import com.alipay.sofa.ark.bootstrap.ClasspathLauncher.ClassPathArchive; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.spi.argument.CommandArgument; import com.alipay.sofa.ark.support.thread.IsolatedThreadGroup; import com.alipay.sofa.ark.support.thread.LaunchRunner; import java.net.URL; import java.net.URLClassLoader; /** * relaunch a started main method with bootstrapping ark container * {@literal org.springframework.boot.maven.RunMojo} * * @author qilong.zql * @author Phillip Webb * @since 0.1.0 */ public class SofaArkBootstrap { private static final String BIZ_CLASSLOADER = "com.alipay.sofa.ark.container.service.classloader.BizClassLoader"; private static final String MAIN_ENTRY_NAME = "remain"; private static EntryMethod entryMethod; public static void launch(String[] args) { try { if (!isSofaArkStarted()) { entryMethod = new EntryMethod(Thread.currentThread()); IsolatedThreadGroup threadGroup = new IsolatedThreadGroup( entryMethod.getDeclaringClassName()); LaunchRunner launchRunner = new LaunchRunner(SofaArkBootstrap.class.getName(), MAIN_ENTRY_NAME, args); Thread launchThread = new Thread(threadGroup, launchRunner, entryMethod.getMethodName()); launchThread.start(); LaunchRunner.join(threadGroup); threadGroup.rethrowUncaughtException(); System.exit(0); } } catch (Throwable e) { throw new RuntimeException(e); } } public static Object prepareContainerForTest(Class testClass) { try { URL[] urls = getURLClassPath(); return new ClasspathLauncher(new ClassPathArchive(testClass.getCanonicalName(), null, urls)).launch(getClasspath(urls), testClass); } catch (Exception e) { throw new RuntimeException(e); } } private static void remain(String[] args) throws Exception {// NOPMD AssertUtils.assertNotNull(entryMethod, "No Entry Method Found."); URL[] urls = getURLClassPath(); new ClasspathLauncher(new ClassPathArchive(entryMethod.getDeclaringClassName(), entryMethod.getMethodName(), urls)).launch(args, getClasspath(urls), entryMethod.getMethod()); } private static String getClasspath(URL[] urls) { StringBuilder sb = new StringBuilder(); for (URL url : urls) { sb.append(url.toExternalForm()).append(CommandArgument.CLASSPATH_SPLIT); } return sb.toString(); } private static URL[] getURLClassPath() { ClassLoader classLoader = ClassLoader.getSystemClassLoader(); // return ((URLClassLoader) classLoader).getURLs(); return ClassLoaderUtils.getURLs(classLoader); } private static boolean isSofaArkStarted() { Class bizClassLoader = SofaArkBootstrap.class.getClassLoader().getClass(); return BIZ_CLASSLOADER.equals(bizClassLoader.getCanonicalName()); } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/thread/IsolatedThreadGroup.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.thread; /** * Isolated {@link ThreadGroup} to capture uncaught exceptions. * {@literal org.springframework.boot.maven.IsolatedThreadGroup} * * @author qilong.zql * @author Phillip Webb * @since 0.1.0 */ public class IsolatedThreadGroup extends ThreadGroup { protected final Object monitor = new Object(); protected Throwable exception; public IsolatedThreadGroup(String name) { super(name); } @Override public void uncaughtException(Thread thread, Throwable ex) { if (!(ex instanceof ThreadDeath)) { synchronized (this.monitor) { this.exception = (this.exception == null ? ex : this.exception); } } } public synchronized void rethrowUncaughtException() { synchronized (this.monitor) { if (this.exception != null) { throw new RuntimeException("An exception occurred while running. " + this.exception.getMessage(), this.exception); } } } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/java/com/alipay/sofa/ark/support/thread/LaunchRunner.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.thread; /** * Runner used to launch the application. * * @author qilong.zql * @since 0.1.0 */ import java.lang.reflect.Method; public class LaunchRunner implements Runnable { private final String startClassName; private final String startMethodName; private final String[] args; public LaunchRunner(String startClassName, String... args) { this(startClassName, "main", args); } public LaunchRunner(String startClassName, String startMethodName, String... args) { this.startClassName = startClassName; this.startMethodName = startMethodName; this.args = (args != null ? args : new String[] {}); } @Override public void run() { Thread thread = Thread.currentThread(); ClassLoader classLoader = thread.getContextClassLoader(); try { Class startClass = classLoader.loadClass(this.startClassName); Method entryMethod; try { entryMethod = startClass.getMethod(startMethodName, String[].class); } catch (NoSuchMethodException ex) { entryMethod = startClass.getDeclaredMethod(startMethodName, String[].class); } if (!entryMethod.isAccessible()) { entryMethod.setAccessible(true); } entryMethod.invoke(null, new Object[] { this.args }); } catch (NoSuchMethodException ex) { Exception wrappedEx = new Exception( String.format( "The specified entry class:%s doesn't contain an entry method:%s with appropriate signature.", this.startClassName, this.startMethodName), ex); thread.getThreadGroup().uncaughtException(thread, wrappedEx); } catch (Throwable ex) { thread.getThreadGroup().uncaughtException(thread, ex); } } /** * Ark container main thread can exit only other threads exit. * * @param threadGroup */ public static void join(ThreadGroup threadGroup) { boolean hasNonDaemonThreads; do { hasNonDaemonThreads = false; Thread[] threads = new Thread[threadGroup.activeCount()]; threadGroup.enumerate(threads); for (Thread thread : threads) { if (thread != null && !thread.isDaemon()) { try { hasNonDaemonThreads = true; thread.join(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } } while (hasNonDaemonThreads); } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/resources/META-INF/services/org.testng.ITestNGListener ================================================ com.alipay.sofa.ark.support.listener.ArkTestNGAlterSuiteListener com.alipay.sofa.ark.support.listener.ArkTestNGInvokedMethodListener ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/main/resources/META-INF/services/sofa-ark/com.alipay.sofa.ark.spi.service.biz.AddBizToStaticDeployHook ================================================ com.alipay.sofa.ark.support.common.AddBizInResourcesHook ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/test/java/com/alipay/sofa/ark/support/AddBizInResourcesHookTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.spi.archive.BizArchive; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.support.common.AddBizInResourcesHook; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.core.env.Environment; import java.util.List; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.when; /** * @author lianglipeng.llp@alibaba-inc.com * @version $Id: AddBizInResourcesHookTest.java, v 0.1 2024年07月10日 19:56 立蓬 Exp $ */ public class AddBizInResourcesHookTest { AddBizInResourcesHook addBizInResourcesHook = new AddBizInResourcesHook(); @Test public void testGetStaticBizToAdd() throws Exception { // case1: ark is not embed ArkConfigs.setEmbedEnable(false); assertEquals(0,addBizInResourcesHook.getStaticBizToAdd().size()); // case2: ark is embed but set 'EMBED_STATIC_BIZ_IN_RESOURCE_ENABLE' as false ArkConfigs.setEmbedEnable(true); ArkConfigs.putStringValue(Constants.EMBED_STATIC_BIZ_IN_RESOURCE_ENABLE, "false"); // config master biz Biz masterBiz = createTestBizModel("test", "1.0.0", BizState.ACTIVATED, this.getClass().getClassLoader()); try (MockedStatic mockedStatic = Mockito.mockStatic(ArkClient.class)){ mockedStatic.when(ArkClient::getMasterBiz).thenReturn(masterBiz); List bizArchives = addBizInResourcesHook.getStaticBizToAdd(); assertEquals(0, bizArchives.size()); }finally { ArkConfigs.setEmbedEnable(false); } // case3: ark is embed and set 'EMBED_STATIC_BIZ_IN_RESOURCE_ENABLE' as true ArkConfigs.setEmbedEnable(true); ArkConfigs.putStringValue(Constants.EMBED_STATIC_BIZ_IN_RESOURCE_ENABLE, "true"); try (MockedStatic mockedStatic = Mockito.mockStatic(ArkClient.class)){ mockedStatic.when(ArkClient::getMasterBiz).thenReturn(masterBiz); List bizArchives = addBizInResourcesHook.getStaticBizToAdd(); assertEquals(1, bizArchives.size()); }finally { ArkConfigs.setEmbedEnable(false); } } private BizModel createTestBizModel(String bizName, String bizVersion, BizState bizState, ClassLoader classLoader) { BizModel bizModel = new BizModel().setBizState(bizState); bizModel.setBizName(bizName).setBizVersion(bizVersion); bizModel.setClassLoader(classLoader); return bizModel; } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/test/java/com/alipay/sofa/ark/support/DefaultClassLoaderHookTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.common.util.ClassLoaderUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.container.model.BizModel; import com.alipay.sofa.ark.container.pipeline.RegisterServiceStage; import com.alipay.sofa.ark.container.service.ArkServiceContainer; import com.alipay.sofa.ark.container.service.ArkServiceContainerHolder; import com.alipay.sofa.ark.container.service.classloader.BizClassLoader; import com.alipay.sofa.ark.exception.ArkLoaderException; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import com.alipay.sofa.ark.spi.service.extension.ArkServiceLoader; import com.alipay.sofa.ark.spi.service.extension.ExtensionLoaderService; import com.alipay.sofa.ark.support.common.DelegateArkContainer; import com.alipay.sofa.ark.support.common.DelegateToMasterBizClassLoaderHook; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; public class DefaultClassLoaderHookTest { private BizManagerService bizManagerService; private final ArkServiceContainer arkServiceContainer = new ArkServiceContainer(new String[] {}); @Before public void before() { arkServiceContainer.start(); arkServiceContainer.getService(RegisterServiceStage.class).process(null); ArkServiceLoader.setExtensionLoaderService(arkServiceContainer .getService(ExtensionLoaderService.class)); ArkConfigs.setSystemProperty(Constants.BIZ_CLASS_LOADER_HOOK_DIR, DelegateToMasterBizClassLoaderHook.class.getName()); bizManagerService = ArkServiceContainerHolder.getContainer().getService( BizManagerService.class); } @After public void after() { arkServiceContainer.stop(); } public static BizModel createTestBizModel(String bizName, String bizVersion, BizState bizState, URL[] urls) { BizModel bizModel = new BizModel().setBizState(bizState); bizModel.setBizName(bizName).setBizVersion(bizVersion); BizClassLoader bizClassLoader = new BizClassLoader(bizModel.getIdentity(), urls); bizClassLoader.setBizModel(bizModel); bizModel.setClassPath(urls).setClassLoader(bizClassLoader); return bizModel; } @Test public void testLoadClassFromClassLoaderHook() throws Exception { URL bizUrl = this.getClass().getClassLoader().getResource("sample-ark-1.0.0-ark-biz.jar"); URL masterUrl1 = this.getClass().getClassLoader() .getResource("sample-ark-plugin-common-0.5.1.jar"); URL masterUrl2 = this.getClass().getClassLoader().getResource("sofa-ark-sample-springboot-ark-0.3.0.jar"); URL masterUrl3 = this.getClass().getClassLoader().getResource("aopalliance-1.0.jar"); URL masterUrl4 = this.getClass().getClassLoader() .getResource("com.springsource.org.aopalliance-1.0.0.jar"); BizModel bizModel = createTestBizModel("biz A", "1.0.0", BizState.RESOLVED, new URL[] { bizUrl }); bizModel.setDenyImportClasses(StringUtils.EMPTY_STRING); bizModel.setDenyImportPackages(StringUtils.EMPTY_STRING); bizModel.setDenyImportResources(StringUtils.EMPTY_STRING); bizModel .setDeclaredLibraries("sample-ark-plugin-common,com.springsource.org.aopalliance"); List masterUrls = new ArrayList<>(); Enumeration urlEnumeration = this.getClass().getClassLoader().getResources(""); while (urlEnumeration.hasMoreElements()) { URL url = urlEnumeration.nextElement(); masterUrls.add(url); } masterUrls.add(masterUrl1); masterUrls.add(masterUrl2); masterUrls.add(masterUrl3); masterUrls.add(masterUrl4); BizModel masterBizModel = createTestBizModel("master biz", "1.0.0", BizState.RESOLVED, masterUrls.toArray(new URL[0])); masterBizModel.setDenyImportClasses(StringUtils.EMPTY_STRING); masterBizModel.setDenyImportPackages(StringUtils.EMPTY_STRING); masterBizModel.setDenyImportResources(StringUtils.EMPTY_STRING); bizManagerService.registerBiz(bizModel); bizManagerService.registerBiz(masterBizModel); ArkClient.setMasterBiz(masterBizModel); // case 1: find class from multiple libs in master classloader Class adviceClazz = bizModel.getBizClassLoader().loadClass("org.aopalliance.aop.Advice"); Assert.assertEquals(adviceClazz.getClassLoader(), masterBizModel.getBizClassLoader()); // case 2: find class from master but not set provided in biz model Assert.assertThrows(ArkLoaderException.class, () -> bizModel.getBizClassLoader().loadClass("com.alipay.sofa.ark.sample.facade.SamplemasterService")); // case 3: find class from master in classpath Assert.assertEquals(masterBizModel.getBizClassLoader(), bizModel.getBizClassLoader().loadClass(DelegateArkContainer.class.getName()).getClassLoader()); // case 4: find class from master in jar Assert.assertEquals(masterBizModel.getBizClassLoader(), bizModel.getBizClassLoader().loadClass("com.alipay.sofa.ark.sample.common.SampleClassExported").getClassLoader()); // case 5: find resource from master but not set provided in biz model Assert.assertNull(bizModel.getBizClassLoader().getResource("org/slf4j/ILoggerFactory.class")); // case 6: find resource from master in classpath Assert.assertNotNull(bizModel.getBizClassLoader().getResource( "sample-ark-1.0.0-ark-biz.jar")); // case 7: find resource from master in multiple jar Assert.assertNull(bizModel.getBizClassLoader().getResource("Sample_Resource_Exported_A")); // case 8: find resources from master but not set provided in biz model Assert.assertFalse(bizModel.getBizClassLoader().getResources("org/slf4j/ILoggerFactory.class") .hasMoreElements()); // case 9: find resources from master in classpath Assert.assertTrue(bizModel.getBizClassLoader().getResources("sample-ark-1.0.0-ark-biz.jar") .hasMoreElements()); // case 10: find resources from master in jar Assert.assertTrue(bizModel.getBizClassLoader().getResources("Sample_Resource_Exported") .hasMoreElements()); } @Test public void getAllResources() throws IOException { URL bizUrl = this.getClass().getClassLoader().getResource("sample-ark-1.0.0-ark-biz.jar"); URL resourceUrl = this.getClass().getClassLoader() .getResource("sample-ark-plugin-common-0.5.1.jar"); URL[] bizUrls = new URL[] { bizUrl, resourceUrl }; BizModel declaredBiz = createTestBizModel("biz A", "1.0.0", BizState.RESOLVED, bizUrls); declaredBiz.setDeclaredLibraries("sample-ark-plugin-common"); BizModel notDeclaredBiz = createTestBizModel("biz B", "1.0.0", BizState.RESOLVED, bizUrls); notDeclaredBiz.setDeclaredLibraries(""); List masterUrls = new ArrayList<>(); Enumeration urlEnumeration = this.getClass().getClassLoader().getResources(""); while (urlEnumeration.hasMoreElements()) { URL url = urlEnumeration.nextElement(); masterUrls.add(url); } masterUrls.add(resourceUrl); BizModel masterBizModel = createTestBizModel("master biz", "1.0.0", BizState.RESOLVED, masterUrls.toArray(new URL[0])); masterBizModel.setDenyImportClasses(StringUtils.EMPTY_STRING); masterBizModel.setDenyImportPackages(StringUtils.EMPTY_STRING); masterBizModel.setDenyImportResources(StringUtils.EMPTY_STRING); bizManagerService.registerBiz(declaredBiz); bizManagerService.registerBiz(notDeclaredBiz); bizManagerService.registerBiz(masterBizModel); ArkClient.setMasterBiz(masterBizModel); Assert.assertTrue(masterBizModel.getBizClassLoader() .getResources("Sample_Resource_Exported").hasMoreElements()); // declaredMode: get all resources from biz and master biz Enumeration resourcesFromBizAndMasterBiz = declaredBiz.getBizClassLoader() .getResources("Sample_Resource_Exported"); Assert.assertNotNull(resourcesFromBizAndMasterBiz.nextElement()); // declaredMode: get all resources from biz and master biz Enumeration resourcesOnlyFromBiz = notDeclaredBiz.getBizClassLoader().getResources( "Sample_Resource_Exported"); Assert.assertNotNull(resourcesOnlyFromBiz.nextElement()); Assert.assertFalse(resourcesOnlyFromBiz.hasMoreElements()); } @Test public void testPostFindCglibClass() throws ClassNotFoundException { URL bizUrl = this.getClass().getClassLoader().getResource("sample-ark-1.0.0-ark-biz.jar"); BizModel testBiz = createTestBizModel("biz A", "1.0.0", BizState.RESOLVED, new URL[] { bizUrl }); testBiz.setDenyImportClasses(StringUtils.EMPTY_STRING); testBiz.setDenyImportPackages(StringUtils.EMPTY_STRING); testBiz.setDenyImportResources(StringUtils.EMPTY_STRING); URL[] urLs = ClassLoaderUtils.getURLs(this.getClass().getClassLoader()); BizModel masterBiz = createTestBizModel("master biz", "1.0.0", BizState.RESOLVED, urLs); masterBiz.setClassLoader(this.getClass().getClassLoader()); bizManagerService.registerBiz(testBiz); bizManagerService.registerBiz(masterBiz); ArkClient.setMasterBiz(masterBiz); Assert.assertThrows(ArkLoaderException.class, () -> testBiz.getBizClassLoader().loadClass("xxxxCGLIB$$")); } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/test/java/com/alipay/sofa/ark/support/MultiSuiteTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support; import com.alipay.sofa.ark.support.common.DelegateArkContainer; import com.alipay.sofa.ark.support.listener.ArkTestNGAlterSuiteListener; import org.junit.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.testng.xml.XmlClass; import org.testng.xml.XmlSuite; import org.testng.xml.XmlTest; import java.util.Collections; import java.util.List; /** * @author qilong.zql * @since 0.3.0 */ public class MultiSuiteTest { private XmlSuite clown = new XmlSuite(); private XmlSuite parent = new XmlSuite(); private XmlSuite child = new XmlSuite(); @BeforeMethod public void init() { clown.setParentSuite(parent); clown.getChildSuites().add(child); clown.setTests(generateXmlTest(MultiSuiteTest.class)); parent.setTests(generateXmlTest(TestNGOnArkTest.class)); child.setTests(generateXmlTest(TestNGCommonTest.class)); } protected List generateXmlTest(Class testClass) { XmlTest xmlTest = new XmlTest(); XmlClass xmlClass = new XmlClass(); xmlClass.setClass(testClass); xmlTest.setClasses(Collections.singletonList(xmlClass)); return Collections.singletonList(xmlTest); } @Test public void test() { new ArkTestNGAlterSuiteListener().alter(Collections.singletonList(clown)); Assert.assertTrue(clown.getTests().get(0).getClasses().get(0).getSupportClass() .getClassLoader().equals(ClassLoader.getSystemClassLoader())); Assert.assertTrue(parent.getTests().get(0).getClasses().get(0).getSupportClass() .getClassLoader().equals(DelegateArkContainer.getTestClassLoader())); Assert.assertTrue(child.getTests().get(0).getClasses().get(0).getSupportClass() .getClassLoader().equals(ClassLoader.getSystemClassLoader())); } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/test/java/com/alipay/sofa/ark/support/TestNGCommonTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support; import org.testng.Assert; import org.testng.annotations.Test; /** * TestNGCommonTest * @author qilong.zql 18/4/26-上午8:58 */ public class TestNGCommonTest { @Test public void test() { ClassLoader threadClassLoader = Thread.currentThread().getContextClassLoader(); ClassLoader thisClassLoader = this.getClass().getClassLoader(); Assert.assertTrue(threadClassLoader.equals(ClassLoader.getSystemClassLoader())); Assert.assertTrue(thisClassLoader.equals(ClassLoader.getSystemClassLoader())); } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/test/java/com/alipay/sofa/ark/support/TestNGOnArkTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support; import com.alipay.sofa.ark.container.test.TestClassLoader; import com.alipay.sofa.ark.support.listener.TestNGOnArk; import org.testng.Assert; import org.testng.annotations.Test; /** * @author qilong.zql * @since 0.1.0 */ @TestNGOnArk public class TestNGOnArkTest { @Test public void test() { ClassLoader threadClassLoader = Thread.currentThread().getContextClassLoader(); ClassLoader thisClassLoader = this.getClass().getClassLoader(); Assert.assertTrue(threadClassLoader.getClass().getCanonicalName() .equals(TestClassLoader.class.getCanonicalName())); Assert.assertTrue(thisClassLoader.getClass().getCanonicalName() .equals(TestClassLoader.class.getCanonicalName())); } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/test/java/com/alipay/sofa/ark/support/runner/ArkJUnit4RunnerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.runner; import com.alipay.sofa.ark.bootstrap.ContainerClassLoader; import com.alipay.sofa.ark.container.test.TestClassLoader; import org.junit.*; import org.junit.runner.RunWith; import org.junit.runners.model.InitializationError; /** * @author qilong.zql * @since 0.3.0 */ @RunWith(ArkJUnit4Runner.class) public class ArkJUnit4RunnerTest { private static String state; @BeforeClass public static void beforeClass() { state = "@BeforeClass"; } @Before public void before() { state = "@Before"; } @Test public void test() { ClassLoader testClassLoader = getClass().getClassLoader(); Assert.assertTrue(testClassLoader.getClass().getCanonicalName() .equals(TestClassLoader.class.getCanonicalName())); Assert.assertTrue("@Before".equals(state)); state = "@Test"; ClassLoader testClCl = testClassLoader.getClass().getClassLoader(); Assert.assertTrue(testClCl.getClass().getCanonicalName() .equals(ContainerClassLoader.class.getCanonicalName())); } @Test public void testJUnitRunner() { try { Assert.assertTrue("@Before".equals(state)); state = "@Test"; ArkJUnit4Runner runner = new ArkJUnit4Runner(ArkJUnit4RunnerTest.class); ClassLoader loader = runner.getTestClass().getJavaClass().getClassLoader(); Assert.assertTrue(loader.getClass().getCanonicalName() .equals(TestClassLoader.class.getCanonicalName())); } catch (InitializationError error) { Assert.fail(error.getMessage()); } } @After public void after() { Assert.assertTrue("@Test".equals(state)); state = "@After"; } @AfterClass public static void afterClass() { Assert.assertTrue("@After".equals(state)); } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/test/java/com/alipay/sofa/ark/support/runner/CommonJUnit4Test.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.runner; import org.junit.Assert; import org.junit.Test; /** * @author qilong.zql * @since 0.3.0 */ public class CommonJUnit4Test { @Test public void testClassLoader() { Assert.assertTrue(Thread.currentThread().getContextClassLoader() .equals(Test.class.getClassLoader())); } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/test/java/com/alipay/sofa/ark/support/startup/EmbedSofaArkBootstrapTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.startup; import com.alipay.sofa.ark.container.ArkContainer; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.alipay.sofa.ark.support.startup.EmbedSofaArkBootstrap.deployStaticBizAfterEmbedMasterBizStarted; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class EmbedSofaArkBootstrapTest { private ArkContainer arkContainer = mock(ArkContainer.class); private Object originalArkContainer; @Before public void setUp() { originalArkContainer = EmbedSofaArkBootstrap.arkContainer; } @After public void tearDown() { EmbedSofaArkBootstrap.arkContainer = originalArkContainer; } @Test public void testDeployStaticBizAfterEmbedMasterBizStarted() throws Exception { when(arkContainer.deployBizAfterMasterBizReady()).thenReturn(arkContainer); EmbedSofaArkBootstrap.arkContainer = arkContainer; deployStaticBizAfterEmbedMasterBizStarted(); } @Test(expected = RuntimeException.class) public void testDeployStaticBizAfterEmbedMasterBizStartedWithNull() throws Exception { EmbedSofaArkBootstrap.arkContainer = null; deployStaticBizAfterEmbedMasterBizStarted(); } @Test public void testEntryMethod() { EntryMethod entryMethod = new EntryMethod(); assertTrue(entryMethod.getMethodName().contains("main")); } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/test/java/com/alipay/sofa/ark/support/thread/IsolatedThreadGroupTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.thread; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author lylingzhen */ public class IsolatedThreadGroupTest { private IsolatedThreadGroup isolatedThreadGroup = new IsolatedThreadGroup("group_1"); @Test public void testUncaughtException() { Exception e = new Exception(); isolatedThreadGroup.uncaughtException(new Thread(), e); assertEquals(e, isolatedThreadGroup.exception); } @Test public void testRethrowUncaughtException() { testUncaughtException(); try { isolatedThreadGroup.rethrowUncaughtException(); assertTrue(false); } catch (RuntimeException re) { assertEquals(re.getCause(), isolatedThreadGroup.exception); } } } ================================================ FILE: sofa-ark-parent/support/ark-support-starter/src/test/java/com/alipay/sofa/ark/support/thread/LaunchRunnerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.support.thread; import org.junit.Assert; import org.junit.Before; import org.junit.Test; /** * @author qilong.zql * @since 0.1.0 */ public class LaunchRunnerTest { public static int count = 0; public final Object lock = new Object(); public static void add(String[] args) { if (args.length > 0) { LaunchRunnerTest.count += Integer.valueOf(args[0]); } } @Before public void init() { LaunchRunnerTest.count = 0; } @Test public void testMainWithNoParameters() { synchronized (lock) { LaunchRunner launchRunner = new LaunchRunner(MainClass.class.getName()); launchRunner.run(); Assert.assertTrue(LaunchRunnerTest.count == 0); } } @Test public void testMainWithParameters() { synchronized (lock) { LaunchRunner launchRunner = new LaunchRunner(MainClass.class.getName(), new String[] { "10" }); launchRunner.run(); Assert.assertTrue(LaunchRunnerTest.count == 10); } } @Test public void testNotMainMethodWithNoParameters() { synchronized (lock) { LaunchRunner launchRunner = new LaunchRunner(LaunchRunnerTest.class.getName(), "add", new String[] {}); launchRunner.run(); Assert.assertTrue(LaunchRunnerTest.count == 0); } } @Test public void testNotMainMethodWithParameters() { synchronized (lock) { LaunchRunner launchRunner = new LaunchRunner(LaunchRunnerTest.class.getName(), "add", new String[] { "10" }); launchRunner.run(); Assert.assertTrue(LaunchRunnerTest.count == 10); } } public static class MainClass { private static void main(String[] args) { if (args.length > 0) { LaunchRunnerTest.count += Integer.valueOf(args[0]); } } } @Test public void testRunWithException() { new LaunchRunner(MainClass.class.getName(), "a", null).run(); new LaunchRunner("a", null).run(); } @Test public void testJoin() { ThreadGroup threadGroup = new ThreadGroup("test_thread_group"); new Thread(threadGroup, "thread_1").start(); new Thread(threadGroup, "thread_2").start(); new LaunchRunner(MainClass.class.getName(), null).join(threadGroup); } } ================================================ FILE: sofa-ark-parent/support/ark-tools/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `sofa-ark-tools` **Package**: `com.alipay.sofa.ark.tools` This module provides the core repackaging utilities for creating Ark Fat JARs. It is used by both `ark-maven-plugin` and `ark-plugin-maven-plugin`. ## Purpose - Repackage standard JARs into Ark Fat JAR format - Layout management for different archive types - Main class discovery - Git information embedding ## Key Classes ### `Repackager` Core class that transforms a regular JAR into an Ark package: - `repackage(File target, File module, Libraries libraries)` - Perform repackaging - `setBizName(String)` / `setBizVersion(String)` - Set module identity - `setMainClass(String)` - Set entry point class - `setDenyImportPackages/classes/Resources` - Configure classloader filtering - `setDeclaredMode(boolean)` - Enable declared dependency mode ### Layouts (`Layouts.java`) Define JAR structure for different archive types: - `ARK_EXECUTABLE` - Executable Ark JAR structure - `ARK_BIZ` - Business module JAR structure - `ARK_PLUGIN` - Plugin JAR structure ### `JarWriter` Lower-level JAR writing utility: - Write entries to JAR - Copy libraries - Write manifest ### `MainClassFinder` Scan class files to find main method entry point. ### `git.JGitParser` Parse Git repository information to embed in JAR: - Commit hash, branch, author - Build time information ### `ArtifactItem` Represents a Maven artifact with groupId, artifactId, version, classifier. ## Dependencies - `sofa-ark-common` - Utilities - `spring-boot-loader` - JAR layout from Spring Boot - `org.eclipse.jgit` - Git information parsing ## Used By - `ark-maven-plugin` - Uses Repackager for business modules - `ark-plugin-maven-plugin` - Uses Repackager for plugins ================================================ FILE: sofa-ark-parent/support/ark-tools/pom.xml ================================================ 4.0.0 sofa-ark-support com.alipay.sofa ${sofa.ark.version} sofa-ark-tools ${project.groupId}:${project.artifactId} com.alipay.sofa sofa-ark-spi com.alipay.sofa sofa-ark-common org.ow2.asm asm org.apache.maven maven-artifact junit junit test org.eclipse.jgit org.eclipse.jgit ================================================ FILE: sofa-ark-parent/support/ark-tools/src/main/java/com/alipay/sofa/ark/tools/ArtifactItem.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; import com.alipay.sofa.ark.common.util.AssertUtils; import org.apache.commons.lang3.StringUtils; import org.apache.maven.artifact.Artifact; import java.util.Objects; /** * @author qilong.zql * @since 0.1.0 */ public class ArtifactItem { private static final String GAV_SPLIT = ":"; private static final String DEFAULT_VERSION = "?"; private String groupId; private String artifactId; private String version = DEFAULT_VERSION; private String classifier; private String type = "jar"; private String scope = "compile"; public String getGroupId() { return groupId; } public void setGroupId(String groupId) { this.groupId = groupId; } public String getArtifactId() { return artifactId; } public void setArtifactId(String artifactId) { this.artifactId = artifactId; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public String getClassifier() { return classifier; } public void setClassifier(String classifier) { this.classifier = classifier; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getScope() { return scope; } public void setScope(String scope) { this.scope = scope; } public String toString() { if (this.classifier == null) { return String.format("%s%s%s%s%s%s%s", groupId, GAV_SPLIT, artifactId, GAV_SPLIT, version, GAV_SPLIT, type); } else { return String.format("%s%s%s%s%s%s%s%s%s", groupId, GAV_SPLIT, artifactId, GAV_SPLIT, classifier, GAV_SPLIT, version, GAV_SPLIT, type); } } public boolean isSameIgnoreVersion(ArtifactItem that) { if (that == null) { return false; } return isSameStr(this.getGroupId(), that.getGroupId()) && isSameStr(this.getArtifactId(), that.getArtifactId()) && isSameStr(this.getClassifier(), that.getClassifier()); } public boolean isSameWithVersion(ArtifactItem that) { if (that == null) { return false; } return isSameStr(this.getGroupId(), that.getGroupId()) && isSameStr(this.getArtifactId(), that.getArtifactId()) && (StringUtils.equals(this.getVersion(), DEFAULT_VERSION) || isSameStr( this.getVersion(), that.getVersion())) && isSameStr(this.getClassifier(), that.getClassifier()); } protected boolean isSameStr(String left, String right) { if ("*".equals(left) || "*".equals(right)) { return true; } return StringUtils.equals(left, right); } /** * parse string pattern {groupId:artifactId} or {groupId:artifactId:classifier} * @param s location pattern * @return */ public static ArtifactItem parseArtifactItemIgnoreVersion(String s) { String[] arr = new String[] {}; if (s != null && !s.isEmpty()) { arr = s.split(GAV_SPLIT); } // groupId, artifactId and classifier(optional) AssertUtils.isTrue(arr != null && arr.length >= 2 && arr.length <= 3, "artifact item format error: %s", s); ArtifactItem item = new ArtifactItem(); item.setGroupId(arr[0]); item.setArtifactId(arr[1]); if (arr.length == 3) { item.setClassifier(arr[2]); } return item; } /** * parse string pattern {groupId:artifactId:version} or {groupId:artifactId:version:classifier} * @param s location pattern * @return */ public static ArtifactItem parseArtifactItemWithVersion(String s) { String[] arr = new String[] {}; if (s != null && !s.isEmpty()) { arr = s.split(GAV_SPLIT); } // groupId, artifactId, version and classifier(optional) AssertUtils.isTrue(arr != null && arr.length >= 3 && arr.length <= 4, "artifact item format error: %s", s); ArtifactItem item = new ArtifactItem(); item.setGroupId(arr[0]); item.setArtifactId(arr[1]); item.setVersion(arr[2]); if (arr.length == 4) { item.setClassifier(arr[3]); } return item; } /** * parse string pattern {groupId:artifactId} {groupId:artifactId:version} or {groupId:artifactId:version:classifier} * @param s location pattern * @return */ public static ArtifactItem parseArtifactItem(String s) { String[] arr = StringUtils.split(s, GAV_SPLIT); // groupId, artifactId, version(optional) and classifier(optional) AssertUtils.isTrue(arr != null && arr.length >= 2 && arr.length <= 4, "artifact item format error: %s", s); ArtifactItem item = new ArtifactItem(); item.setGroupId(arr[0]); item.setArtifactId(arr[1]); if (arr.length >= 3) { item.setVersion(arr[2]); } if (arr.length == 4) { item.setClassifier(arr[3]); } return item; } public static ArtifactItem parseArtifactItem(Artifact artifact) { ArtifactItem artifactItem = new ArtifactItem(); artifactItem.setGroupId(artifact.getGroupId()); artifactItem.setArtifactId(artifact.getArtifactId()); artifactItem.setClassifier(artifact.getClassifier()); artifactItem.setVersion(artifact.getVersion()); artifactItem.setType(artifact.getType()); artifactItem.setScope(artifact.getScope()); return artifactItem; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ArtifactItem artifactItem = (ArtifactItem) o; return Objects.equals(this.groupId, artifactItem.getGroupId()) && Objects.equals(this.artifactId, artifactItem.getArtifactId()) && Objects.equals(this.type, artifactItem.getType()) && Objects.equals(this.version, artifactItem.getVersion()) && Objects.equals(this.classifier, artifactItem.getClassifier()) && Objects.equals(this.scope, artifactItem.getScope()); } @Override public int hashCode() { return Objects .hash(this.groupId, this.artifactId, this.type, this.version, this.classifier); } } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/main/java/com/alipay/sofa/ark/tools/JarWriter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.spi.constant.Constants; import java.io.*; import java.util.Arrays; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import java.util.jar.*; import java.util.zip.CRC32; import java.util.zip.ZipEntry; /** * Writes JAR content, ensuring valid directory entries are always create and duplicate * * @author Phillip Webb * @author Andy Wilkinson */ public class JarWriter implements LoaderClassesWriter { private static final String NESTED_ARCHIVE_LOADER_JAR = "sofa-ark-archive"; private static final String NESTED_SPI_LOADER_JAR = "sofa-ark-spi"; private static final String NESTED_COMMON_LOADER_JAR = "sofa-ark-common"; private static final String NESTED_ARCHIVE_LOADER_CLASS_PREFIX = "com/alipay/sofa/ark/bootstrap"; private static final String NESTED_ARCHIVE_BOOTSTRAP_CLASS_PREFIX = "com/alipay/sofa/ark/loader"; private static final String NESTED_SPI_ARCHIVE_LOADER_CLASS_PREFIX = "com/alipay/sofa/ark/spi/archive"; private static final String NESTED_ARCHIVE_STRING_UTIL_CLASS_PREFIX = "com/alipay/sofa/ark/common/util/StringUtils"; private static final String NESTED_ARCHIVE_ASSERT_UTIL_CLASS_PREFIX = "com/alipay/sofa/ark/common/util/AssertUtils"; private static final String NESTED_SPI_CONSTANT_CLASS_PREFIX = "com/alipay/sofa/ark/spi/constant"; private static final int BUFFER_SIZE = 32 * 1024; private final JarOutputStream jarOutput; private final Set writtenEntries = new HashSet<>(); /** * Create a new {@link JarWriter} instance. * * @param file the file to write * @throws IOException if the file cannot be opened * @throws FileNotFoundException if the file cannot be found */ public JarWriter(File file) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream(file); this.jarOutput = new JarOutputStream(fileOutputStream); } /** * Write the specified manifest. * * @param manifest the manifest to write * @throws IOException of the manifest cannot be written */ public void writeManifest(final Manifest manifest) throws IOException { JarEntry entry = new JarEntry("META-INF/MANIFEST.MF"); writeEntry(entry, new EntryWriter() { @Override public void write(OutputStream outputStream) throws IOException { manifest.write(outputStream); } }); } public void writeMarkEntry() throws IOException { String str = "a mark file included in sofa-ark module."; writeEntry(Constants.ARK_BIZ_MARK_ENTRY, new ByteArrayInputStream(str.toString().getBytes())); } /** * Write all entries from the specified jar file. * * @param jarFile the source jar file * @throws IOException if the entries cannot be written */ public void writeEntries(JarFile jarFile) throws IOException { this.writeEntries(jarFile, new IdentityEntryTransformer()); } public void writeBootstrapEntry(JarFile arkContainerJar) throws IOException { Enumeration entries = arkContainerJar.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry.getName().contains(NESTED_ARCHIVE_LOADER_JAR) || entry.getName().contains(NESTED_SPI_LOADER_JAR) || entry.getName().contains(NESTED_COMMON_LOADER_JAR)) { JarInputStream inputStream = new JarInputStream(new BufferedInputStream( arkContainerJar.getInputStream(entry))); writeLoaderClasses(inputStream); } } } public void writeEntries(JarFile jarFile, EntryTransformer entryTransformer) throws IOException { Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream( jarFile.getInputStream(entry)); try { if (inputStream.hasZipHeader() && entry.getMethod() != ZipEntry.STORED) { new CrcAndSize(inputStream).setupStoredEntry(entry); inputStream.close(); inputStream = new ZipHeaderPeekInputStream(jarFile.getInputStream(entry)); } EntryWriter entryWriter = new InputStreamEntryWriter(inputStream, true); JarEntry transformedEntry = entryTransformer.transform(entry); if (transformedEntry != null) { writeEntry(transformedEntry, entryWriter); } } finally { inputStream.close(); } } } /** * Writes an entry. The {@code inputStream} is closed once the entry has been written * * @param entryName The name of the entry * @param inputStream The stream from which the entry's data can be read * @throws IOException if the write fails */ @Override public void writeEntry(String entryName, InputStream inputStream) throws IOException { JarEntry entry = new JarEntry(entryName); writeEntry(entry, new InputStreamEntryWriter(inputStream, true)); } /** * Write a nested library. * * @param destination the destination of the library * @param library the library * @throws IOException if the write fails */ public void writeNestedLibrary(String destination, Library library) throws IOException { File file = library.getFile(); JarEntry entry = new JarEntry(destination + library.getName()); entry.setTime(getNestedLibraryTime(file)); if (library.isUnpackRequired()) { entry.setComment("UNPACK:" + FileUtils.sha1Hash(file)); } new CrcAndSize(file).setupStoredEntry(entry); writeEntry(entry, new InputStreamEntryWriter(new FileInputStream(file), true)); } private long getNestedLibraryTime(File file) { try (JarFile jarFile = new JarFile(file)) { Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (!entry.isDirectory()) { return entry.getTime(); } } } catch (Exception ex) { // Ignore and just use the source file timestamp } return file.lastModified(); } /** * * @param jarInputStream the inputStream of the resource containing the loader classes * to be written * @throws IOException */ @Override public void writeLoaderClasses(JarInputStream jarInputStream) throws IOException { JarEntry entry; while ((entry = jarInputStream.getNextJarEntry()) != null) { if (entry.getName().endsWith(".class") && (entry.getName().contains(NESTED_SPI_ARCHIVE_LOADER_CLASS_PREFIX) || entry.getName().contains(NESTED_ARCHIVE_BOOTSTRAP_CLASS_PREFIX) || entry.getName().contains(NESTED_ARCHIVE_LOADER_CLASS_PREFIX) || entry.getName().contains(NESTED_ARCHIVE_STRING_UTIL_CLASS_PREFIX) || entry.getName().contains(NESTED_ARCHIVE_ASSERT_UTIL_CLASS_PREFIX) || entry .getName().contains(NESTED_SPI_CONSTANT_CLASS_PREFIX))) { writeEntry(entry, new InputStreamEntryWriter(jarInputStream, false)); } } jarInputStream.close(); } /** * Close the writer. * * @throws IOException if the file cannot be closed */ public void close() throws IOException { this.jarOutput.close(); } /** * Perform the actual write of a {@link JarEntry}. All other {@code write} method * delegate to this one. * * @param entry the entry to write * @param entryWriter the entry writer or {@code null} if there is no content * @throws IOException in case of I/O errors */ private void writeEntry(JarEntry entry, EntryWriter entryWriter) throws IOException { String parent = entry.getName(); if (parent.endsWith("/")) { parent = parent.substring(0, parent.length() - 1); } if (parent.lastIndexOf("/") != -1) { parent = parent.substring(0, parent.lastIndexOf("/") + 1); if (parent.length() > 0) { writeEntry(new JarEntry(parent), null); } } if (this.writtenEntries.add(entry.getName())) { this.jarOutput.putNextEntry(entry); if (entryWriter != null) { entryWriter.write(this.jarOutput); } this.jarOutput.closeEntry(); } } /** * Interface used to write jar entry date. */ private interface EntryWriter { /** * Write entry data to the specified output stream. * * @param outputStream the destination for the data * @throws IOException in case of I/O errors */ void write(OutputStream outputStream) throws IOException; } /** * {@link EntryWriter} that writes content from an {@link InputStream}. */ private static class InputStreamEntryWriter implements EntryWriter { private final InputStream inputStream; private final boolean close; InputStreamEntryWriter(InputStream inputStream, boolean close) { this.inputStream = inputStream; this.close = close; } @Override public void write(OutputStream outputStream) throws IOException { byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead; while ((bytesRead = this.inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.flush(); if (this.close) { this.inputStream.close(); } } } /** * {@link InputStream} that can peek ahead at zip header bytes. */ static class ZipHeaderPeekInputStream extends FilterInputStream { private static final byte[] ZIP_HEADER = new byte[] { 0x50, 0x4b, 0x03, 0x04 }; private final byte[] header; private ByteArrayInputStream headerStream; protected ZipHeaderPeekInputStream(InputStream in) throws IOException { super(in); this.header = new byte[4]; int len = in.read(this.header); this.headerStream = new ByteArrayInputStream(this.header, 0, len); } @Override public int read() throws IOException { int read = (this.headerStream == null ? -1 : this.headerStream.read()); if (read != -1) { this.headerStream = null; return read; } return super.read(); } @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } @Override public int read(byte[] b, int off, int len) throws IOException { int read = (this.headerStream == null ? -1 : this.headerStream.read(b, off, len)); if (read != -1) { this.headerStream = null; return read; } return super.read(b, off, len); } public boolean hasZipHeader() { return Arrays.equals(this.header, ZIP_HEADER); } } /** * Data holder for CRC and Size. */ private static class CrcAndSize { private final CRC32 crc = new CRC32(); private long size; CrcAndSize(File file) throws IOException { FileInputStream inputStream = new FileInputStream(file); try { load(inputStream); } finally { inputStream.close(); } } CrcAndSize(InputStream inputStream) throws IOException { load(inputStream); } private void load(InputStream inputStream) throws IOException { byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { this.crc.update(buffer, 0, bytesRead); this.size += bytesRead; } } public void setupStoredEntry(JarEntry entry) { entry.setSize(this.size); entry.setCompressedSize(this.size); entry.setCrc(this.crc.getValue()); entry.setMethod(ZipEntry.STORED); } } /** * An {@code EntryTransformer} enables the transformation of {@link JarEntry jar * entries} during the writing process. */ public interface EntryTransformer { /** * Transform {@link JarEntry} * * @param jarEntry * @return */ JarEntry transform(JarEntry jarEntry); } /** * An {@code EntryTransformer} that returns the entry unchanged. */ public static final class IdentityEntryTransformer implements EntryTransformer { @Override public JarEntry transform(JarEntry jarEntry) { return jarEntry; } } } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/main/java/com/alipay/sofa/ark/tools/Layout.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; /** * Strategy interface used to determine the layout for a particular type of archive. * * @author qilong.zql * @since 0.1.0 */ public interface Layout { /** * Returns the launcher class name for this layout. * @return the launcher class name */ String getLauncherClassName(); /** * Returns the destination path for a given library. * @param libraryName the name of the library (excluding any path) * @param scope the scope of the library * @return the destination relative to the root of the archive (should end with '/') * or {@code null} if the library should not be included. */ String getLibraryDestination(String libraryName, LibraryScope scope); /** * Returns if loader classes should be included to make the archive executable. * @return if the layout is executable */ boolean isExecutable(); } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/main/java/com/alipay/sofa/ark/tools/Layouts.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; /** * @author qilong.zql * @since 0.1.0 */ public class Layouts { private Layouts() { } /** * Executable JAR layout. */ public static class Jar implements RepackagingLayout { @Override public String getArkContainerLocation() { return "SOFA-ARK/container/"; } @Override public String getArkPluginLocation() { return "SOFA-ARK/plugin/"; } @Override public String getArkModuleLocation() { return "SOFA-ARK/biz/"; } @Override public String getLauncherClassName() { return "com.alipay.sofa.ark.bootstrap.ArkLauncher"; } @Override public String getLibraryDestination(String libraryName, LibraryScope scope) { if (scope.equals(LibraryScope.PLUGIN)) { return getArkPluginLocation(); } else if (scope.equals(LibraryScope.MODULE)) { return getArkModuleLocation(); } else if (scope.equals(LibraryScope.CONTAINER)) { return getArkContainerLocation(); } return ""; } @Override public boolean isExecutable() { return true; } public static Jar jar() { return new Jar(); } } /** * Module layout (designed to be used as a "plug-in") */ public static class Module implements Layout { @Override public String getLauncherClassName() { return ""; } @Override public String getLibraryDestination(String libraryName, LibraryScope scope) { return "lib/"; } @Override public boolean isExecutable() { return false; } public static Module module() { return new Module(); } } } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/main/java/com/alipay/sofa/ark/tools/Libraries.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; import java.io.IOException; /** * Encapsulates process about libraries that may be packed into the archive. * * @author qilong.zql * @since 0.1.0 */ public interface Libraries { /** * Iterate all relevant libraries. * @param callback a callback for each relevant library. * @throws IOException if the operation fails */ void doWithLibraries(LibraryCallback callback) throws IOException; } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/main/java/com/alipay/sofa/ark/tools/Library.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; import java.io.File; /** * Encapsulates information about a single library that may be packed into the archive. * * @author qilong.zql * @since 0.1.0 */ public class Library { private final String name; private String artifactId; private final File file; private LibraryScope scope; private final boolean unpackRequired; /** * Create a new {@link Library}. * @param file the source file * @param scope the scope of the library */ public Library(File file, LibraryScope scope) { this(file, scope, false); } /** * Create a new {@link Library}. * @param file the source file * @param scope the scope of the library * @param unpackRequired if the library needs to be unpacked before it can be used */ public Library(File file, LibraryScope scope, boolean unpackRequired) { this(null, file, scope, unpackRequired); } /** * Create a new {@link Library}. * @param name the name of the library as it should be written or {@code null} to use * the file name * @param file the source file * @param scope the scope of the library * @param unpackRequired if the library needs to be unpacked before it can be used */ public Library(String name, File file, LibraryScope scope, boolean unpackRequired) { this.name = (name == null ? file.getName() : name); this.file = file; this.scope = scope; this.unpackRequired = unpackRequired; } /** * Return the name of file as it should be written. * @return the name */ public String getName() { return this.name; } /** * return the artifact id * @return the artifactId */ public String getArtifactId() { return artifactId; } /** * set artifactId * @param artifactId */ public void setArtifactId(String artifactId) { this.artifactId = artifactId; } /** * Return the library file. * @return the file */ public File getFile() { return this.file; } /** * Return the scope of the library. * @return the scope */ public LibraryScope getScope() { return this.scope; } public void setScope(LibraryScope scope) { this.scope = scope; } /** * Return if the file cannot be used directly as a nested jar and needs to be * unpacked. * @return if unpack is required */ public boolean isUnpackRequired() { return this.unpackRequired; } } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/main/java/com/alipay/sofa/ark/tools/LibraryCallback.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; import java.io.File; import java.io.IOException; /** * Callback interface used to iterate {@link Libraries}. * * @author qilong.zql * @since 0.1.0 */ public interface LibraryCallback { /** * Callback for a single library backed by a {@link File}. * NOTE: library name will be appended maven groupId as prefix, if duplicated artifactId+version was found * * @param library the library * @throws IOException if the operation fails */ void library(Library library) throws IOException; } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/main/java/com/alipay/sofa/ark/tools/LibraryScope.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; /** * @author qilong.zql * @since 0.1.0 */ public interface LibraryScope { /** * The library is used at compile time and runtime */ LibraryScope COMPILE = new LibraryScope() { @Override public String toString() { return "compile"; } }; /** * The library is used at runtime but not needed for compile. */ LibraryScope RUNTIME = new LibraryScope() { @Override public String toString() { return "runtime"; } }; /** * The library is needed for compile but is usually provided when running. */ LibraryScope PROVIDED = new LibraryScope() { @Override public String toString() { return "provided"; } }; /** * Marker for sofa-ark plugin scope when custom configuration is used. */ LibraryScope PLUGIN = new LibraryScope() { @Override public String toString() { return "plugin"; } }; /** * Marker for sofa-ark module scope when custom configuration is used. */ LibraryScope MODULE = new LibraryScope() { @Override public String toString() { return "module"; } }; /** * Marker for sofa-ark container scope when custom configuration is used. */ LibraryScope CONTAINER = new LibraryScope() { @Override public String toString() { return "container"; } }; } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/main/java/com/alipay/sofa/ark/tools/LoaderClassesWriter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; import java.io.IOException; import java.io.InputStream; import java.util.jar.JarInputStream; /** * Writer to write classes into repackaged JAR. * * @author qilong.zql * @since 0.1.0 */ public interface LoaderClassesWriter { /** * Write custom required SOFA-ark-loader classes to the JAR. * @param jarInputStream the inputStream of the resource containing the loader classes * to be written * @throws IOException if the classes cannot be written */ void writeLoaderClasses(JarInputStream jarInputStream) throws IOException; /** * Write a single entry to the JAR. * @param name the name of the entry * @param inputStream the input stream content * @throws IOException if the entry cannot be written */ void writeEntry(String name, InputStream inputStream) throws IOException; } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/main/java/com/alipay/sofa/ark/tools/MainClassFinder.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; import org.objectweb.asm.*; import java.io.*; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * Finds any class with a {@code public static main} method by performing a breadth first * search. * * @author Phillip Webb * @author Andy Wilkinson */ public class MainClassFinder { private static final String DOT_CLASS = ".class"; private static final Type STRING_ARRAY_TYPE = Type.getType(String[].class); private static final Type MAIN_METHOD_TYPE = Type.getMethodType(Type.VOID_TYPE, STRING_ARRAY_TYPE); private static final String MAIN_METHOD_NAME = "main"; /** * Find a single main class in a given jar file. A main class annotated with an * annotation with the given {@code annotationName} will be preferred over a main * class with no such annotation. * @param jarFile the jar file to search * @param classesLocation the location within the jar containing classes * @param annotationName the name of the annotation that may be present on the main * class * @return the main class or {@code null} * @throws IOException if the jar file cannot be read */ public static String findSingleMainClass(JarFile jarFile, String classesLocation, String annotationName) throws IOException { SingleMainClassCallback callback = new SingleMainClassCallback(annotationName); MainClassFinder.doWithMainClasses(jarFile, classesLocation, callback); return callback.getMainClassName(); } /** * Perform the given callback operation on all main classes from the given jar. * @param the result type * @param jarFile the jar file to search * @param classesLocation the location within the jar containing classes * @param callback the callback * @return the first callback result or {@code null} * @throws IOException in case of I/O errors */ static T doWithMainClasses(JarFile jarFile, String classesLocation, MainClassCallback callback) throws IOException { List classEntries = getClassEntries(jarFile, classesLocation); Collections.sort(classEntries, new ClassEntryComparator()); for (JarEntry entry : classEntries) { InputStream inputStream = new BufferedInputStream(jarFile.getInputStream(entry)); try { ClassDescriptor classDescriptor = createClassDescriptor(inputStream); if (classDescriptor != null && classDescriptor.isMainMethodFound()) { String className = convertToClassName(entry.getName(), classesLocation); T result = callback.doWith(new MainClass(className, classDescriptor .getAnnotationNames())); if (result != null) { return result; } } } finally { inputStream.close(); } } return null; } private static String convertToClassName(String name, String prefix) { name = name.replace('/', '.'); name = name.replace('\\', '.'); name = name.substring(0, name.length() - DOT_CLASS.length()); if (prefix != null) { name = name.substring(prefix.length()); } return name; } private static List getClassEntries(JarFile source, String classesLocation) { classesLocation = (classesLocation != null ? classesLocation : ""); Enumeration sourceEntries = source.entries(); List classEntries = new ArrayList<>(); while (sourceEntries.hasMoreElements()) { JarEntry entry = sourceEntries.nextElement(); if (entry.getName().startsWith(classesLocation) && entry.getName().endsWith(DOT_CLASS)) { classEntries.add(entry); } } return classEntries; } private static ClassDescriptor createClassDescriptor(InputStream inputStream) { try { ClassReader classReader = new ClassReader(inputStream); ClassDescriptor classDescriptor = new ClassDescriptor(); classReader.accept(classDescriptor, ClassReader.SKIP_CODE); return classDescriptor; } catch (IOException ex) { return null; } } private static class ClassEntryComparator implements Comparator { @Override public int compare(JarEntry o1, JarEntry o2) { Integer d1 = getDepth(o1); Integer d2 = getDepth(o2); int depthCompare = d1.compareTo(d2); if (depthCompare != 0) { return depthCompare; } return o1.getName().compareTo(o2.getName()); } private int getDepth(JarEntry entry) { return entry.getName().split("/").length; } } private static class ClassDescriptor extends ClassVisitor { private final Set annotationNames = new LinkedHashSet<>(); private boolean mainMethodFound; ClassDescriptor() { super(Opcodes.ASM7); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { this.annotationNames.add(Type.getType(desc).getClassName()); return null; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (isAccess(access, Opcodes.ACC_PUBLIC, Opcodes.ACC_STATIC) && MAIN_METHOD_NAME.equals(name) && MAIN_METHOD_TYPE.getDescriptor().equals(desc)) { this.mainMethodFound = true; } return null; } private boolean isAccess(int access, int... requiredOpsCodes) { for (int requiredOpsCode : requiredOpsCodes) { if ((access & requiredOpsCode) == 0) { return false; } } return true; } boolean isMainMethodFound() { return this.mainMethodFound; } Set getAnnotationNames() { return this.annotationNames; } } /** * Callback for handling {@link MainClass MainClasses}. * * @param the callback's return type */ interface MainClassCallback { /** * Handle the specified main class. * @param mainClass the main class * @return a non-null value if processing should end or {@code null} to continue */ T doWith(MainClass mainClass); } /** * A class with a {@code main} method. */ static final class MainClass { private final String name; private final Set annotationNames; /** * Creates a new {@code MainClass} rather represents the main class with the given * {@code name}. The class is annotated with the annotations with the given * {@code annotationNames}. * @param name the name of the class * @param annotationNames the names of the annotations on the class */ MainClass(String name, Set annotationNames) { this.name = name; this.annotationNames = Collections.unmodifiableSet(new HashSet<>(annotationNames)); } String getName() { return this.name; } Set getAnnotationNames() { return this.annotationNames; } @Override public String toString() { return this.name; } @Override public int hashCode() { return this.name.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } MainClass other = (MainClass) obj; return this.name.equals(other.name); } } /** * Find a single main class, throwing an {@link IllegalStateException} if multiple * candidates exist. */ private static final class SingleMainClassCallback implements MainClassCallback { private final Set mainClasses = new LinkedHashSet<>(); private final String annotationName; private SingleMainClassCallback(String annotationName) { this.annotationName = annotationName; } @Override public Object doWith(MainClass mainClass) { this.mainClasses.add(mainClass); return null; } private String getMainClassName() { Set matchingMainClasses = new LinkedHashSet<>(); if (this.annotationName != null) { for (MainClass mainClass : this.mainClasses) { if (mainClass.getAnnotationNames().contains(this.annotationName)) { matchingMainClasses.add(mainClass); } } } if (matchingMainClasses.isEmpty()) { matchingMainClasses.addAll(this.mainClasses); } if (matchingMainClasses.size() > 1) { throw new IllegalStateException( "Unable to find a single main class from the following candidates " + matchingMainClasses); } return matchingMainClasses.isEmpty() ? null : matchingMainClasses.iterator().next() .getName(); } } } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/main/java/com/alipay/sofa/ark/tools/Repackager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.FileUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.tools.git.GitInfo; import com.alipay.sofa.ark.tools.git.JGitParser; import java.io.*; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import static com.alipay.sofa.ark.spi.constant.Constants.*; /** * Utility class that can be used to repackage an archive so that it can be executed using * {@literal java -jar} * * @author qilong.zql * @since 0.1.0 */ public class Repackager { private static final String ARK_VERSION_ATTRIBUTE = "Sofa-Ark-Version"; private static final String ARK_CONTAINER_ROOT = "Ark-Container-Root"; private static final byte[] ZIP_FILE_HEADER = new byte[] { 'P', 'K', 3, 4 }; private static final long FIND_WARNING_TIMEOUT = TimeUnit.SECONDS .toMillis(10); private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication"; private List mainClassTimeoutListeners = new ArrayList<>(); private String mainClass; private String bizName; private String bizVersion; private String priority; private LinkedHashSet denyImportPackages; private LinkedHashSet denyImportClasses; private LinkedHashSet denyImportResources; private LinkedHashSet injectPluginDependencies = new LinkedHashSet<>(); private LinkedHashSet injectPluginExportPackages = new LinkedHashSet<>(); private LinkedHashSet declaredLibraries = new LinkedHashSet<>(); private final File source; private File executableFatJar; private File pluginModuleJar; private File baseDir; private boolean packageProvided; private boolean skipArkExecutable; private boolean keepArkBizJar; private String webContextPath; private boolean declaredMode; private String arkVersion = null; private Library arkContainerLibrary = null; private final List standardLibraries = new ArrayList<>(); private final List arkPluginLibraries = new ArrayList<>(); private final List arkModuleLibraries = new ArrayList<>(); private File gitDirectory; private GitInfo gitInfo; public Repackager(File source) { if (source == null) { throw new IllegalArgumentException("Source file must be provided"); } if (!source.exists() || !source.isFile()) { throw new IllegalArgumentException("Source must refer to an existing file, " + "got" + source.getAbsolutePath()); } this.source = source.getAbsoluteFile(); } /** * Add a listener that will be triggered to display a warning if searching for the * main class takes too long. * @param listener the listener to add */ public void addMainClassTimeoutWarningListener(MainClassTimeoutWarningListener listener) { this.mainClassTimeoutListeners.add(listener); } /** * Set the main class that should be run. If not specified the value from the * MANIFEST will be used, or if no manifest entry is found the archive will be * searched for a suitable class. * @param mainClass */ public void setMainClass(String mainClass) { this.mainClass = mainClass; } /** * Set the ark-biz name that represents a unique id during runtime, it can be * bind uniquely to a ark-biz * * @param bizName */ public void setBizName(String bizName) { this.bizName = bizName; } public void setBizVersion(String bizVersion) { this.bizVersion = bizVersion; } public void setArkVersion(String arkVersion) { this.arkVersion = arkVersion; } public void setPriority(String priority) { this.priority = priority; } public void setDenyImportPackages(LinkedHashSet denyImportPackages) { this.denyImportPackages = denyImportPackages; } public void setDenyImportClasses(LinkedHashSet denyImportClasses) { this.denyImportClasses = denyImportClasses; } public void setDenyImportResources(LinkedHashSet denyImportResources) { this.denyImportResources = denyImportResources; } public void setInjectPluginExportPackages(LinkedHashSet injectPluginExportPackages) { this.injectPluginExportPackages = injectPluginExportPackages; } public void setInjectPluginDependencies(LinkedHashSet injectPluginDependencies) { if (injectPluginDependencies == null) { return; } for (String artifact : injectPluginDependencies) { ArtifactItem item = new ArtifactItem(); String[] artifactWithVersion = artifact.split(STRING_COLON); AssertUtils.isFalse(artifactWithVersion.length != 2, "injectPluginDependencies item must be follow format by name:version, current is:" + artifact); item.setArtifactId(artifactWithVersion[0]); item.setVersion(artifactWithVersion[1]); this.injectPluginDependencies.add(item); } } public void setGitDirectory(File gitDirectory) { this.gitDirectory = gitDirectory; } public void prepareDeclaredLibraries(Collection artifactItems) { if (!this.declaredMode) { return; } if (artifactItems == null) { return; } for (ArtifactItem artifactItem : artifactItems) { if (artifactItem != null && artifactItem.getArtifactId() != null) { declaredLibraries.add(artifactItem.getArtifactId()); } } } /** * Repackage to the given destination so that it can be launched using ' * {@literal java -jar}'. * * @param appDestination the executable fat jar's destination * @param moduleDestination the 'plug-in' module jar's destination * @param libraries the libraries required to run the archive * @throws IOException if the file cannot be repackaged */ public void repackage(File appDestination, File moduleDestination, Libraries libraries) throws IOException { if (appDestination == null || appDestination.isDirectory() || moduleDestination == null || moduleDestination.isDirectory()) { throw new IllegalArgumentException("Invalid destination"); } if (libraries == null) { throw new IllegalArgumentException("Libraries must not be null"); } if (alreadyRepackaged()) { return; } executableFatJar = appDestination; pluginModuleJar = moduleDestination; libraries.doWithLibraries(new LibraryCallback() { @Override public void library(Library library) throws IOException { if (LibraryScope.PROVIDED.equals(library.getScope()) && !isPackageProvided()) { return; } if (!isZip(library.getFile())) { return; } try (JarFile jarFile = new JarFile(library.getFile())) { if (isArkContainer(jarFile)) { if (arkContainerLibrary != null) { throw new RuntimeException("duplicate SOFAArk Container dependency"); } library.setScope(LibraryScope.CONTAINER); arkContainerLibrary = library; } else if (isArkModule(jarFile)) { library.setScope(LibraryScope.MODULE); arkModuleLibraries.add(library); } else if (isArkPlugin(jarFile)) { library.setScope(LibraryScope.PLUGIN); arkPluginLibraries.add(library); } else { standardLibraries.add(library); } } } }); // 构建信息 gitInfo = JGitParser.parse(gitDirectory); repackageModule(); repackageApp(); removeArkBizJar(); } private void repackageModule() throws IOException { File destination = pluginModuleJar.getAbsoluteFile(); destination.delete(); JarFile jarFileSource = new JarFile(source); JarWriter writer = new JarWriter(destination); Manifest manifest = buildModuleManifest(jarFileSource); try { writer.writeManifest(manifest); writeConfDir(new File(baseDir, Constants.CONF_BASE_DIR), writer); writer.writeEntries(jarFileSource); writer.writeMarkEntry(); writeNestedLibraries(standardLibraries, Layouts.Module.module(), writer); } finally { jarFileSource.close(); try { writer.close(); } catch (Exception ex) { // Ignore } } } private List getModuleLibraries() { List libraries = new ArrayList<>(arkModuleLibraries); Library moduleLibrary = new Library(pluginModuleJar.getAbsoluteFile(), LibraryScope.MODULE); libraries.add(moduleLibrary); return libraries; } private void repackageApp() throws IOException { if (skipArkExecutable) { return; } File destination = executableFatJar.getAbsoluteFile(); destination.delete(); JarFile jarFileSource = new JarFile(arkContainerLibrary.getFile().getAbsoluteFile()); JarWriter writer = new JarWriter(destination); Manifest manifest = buildAppManifest(new JarFile(pluginModuleJar)); try { writer.writeManifest(manifest); writeConfDir(new File(baseDir, Constants.CONF_BASE_DIR), writer); writer.writeBootstrapEntry(jarFileSource); writeNestedLibraries(Collections.singletonList(arkContainerLibrary), Layouts.Jar.jar(), writer); writeNestedLibraries(arkPluginLibraries, Layouts.Jar.jar(), writer); writeNestedLibraries(getModuleLibraries(), Layouts.Jar.jar(), writer); } finally { jarFileSource.close(); try { writer.close(); } catch (Exception ex) { // Ignore } } } private void removeArkBizJar() { if (!keepArkBizJar) { pluginModuleJar.getAbsoluteFile().deleteOnExit(); } } private void writeConfDir(File confDir, JarWriter jarWriter) throws IOException { if (!confDir.exists()) { return; } for (File subFile : confDir.listFiles()) { if (subFile.isDirectory()) { writeConfDir(subFile, jarWriter); } else { String entryName = subFile.getPath().substring(baseDir.getPath().length()); if (entryName.startsWith(File.separator)) { entryName = entryName.substring(1); } jarWriter.writeEntry(FileUtils.getCompatiblePath(entryName), new FileInputStream( subFile)); } } } private void writeNestedLibraries(List libraries, Layout layout, JarWriter writer) throws IOException { Set alreadySeen = new HashSet<>(); for (Library library : libraries) { String destination = layout .getLibraryDestination(library.getName(), library.getScope()); if (destination != null) { if (!alreadySeen.add(destination + library.getName())) { throw new IllegalStateException("Duplicate library " + library.getName()); } boolean isWrite = false; for (ArtifactItem item : injectPluginDependencies) { if (library.getName().equals( item.getArtifactId() + "-" + item.getVersion() + ".jar")) { writer.writeNestedLibrary(destination + "export/", library); isWrite = true; break; } } if (!isWrite) { writer.writeNestedLibrary(destination, library); } } } } @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean isZip(File file) { try { FileInputStream fileInputStream = new FileInputStream(file); try { return isZip(fileInputStream); } finally { fileInputStream.close(); } } catch (IOException ex) { return false; } } private boolean isArkContainer(JarFile jarFile) { return jarFile.getEntry(Constants.ARK_CONTAINER_MARK_ENTRY) != null; } private boolean isArkPlugin(JarFile jarFile) { return jarFile.getEntry(Constants.ARK_PLUGIN_MARK_ENTRY) != null; } private boolean isArkModule(JarFile jarFile) { return jarFile.getEntry(Constants.ARK_BIZ_MARK_ENTRY) != null; } public static boolean isZip(InputStream inputStream) throws IOException { for (int i = 0; i < ZIP_FILE_HEADER.length; i++) { if (inputStream.read() != ZIP_FILE_HEADER[i]) { return false; } } return true; } public Manifest buildModuleManifest(JarFile source) throws IOException { Manifest manifest = source.getManifest(); if (manifest == null) { manifest = new Manifest(); manifest.getMainAttributes().putValue("Manifest-Version", "1.0"); } manifest = new Manifest(manifest); String startClass = this.mainClass; if (startClass == null) { startClass = manifest.getMainAttributes().getValue(MAIN_CLASS_ATTRIBUTE); } if (startClass == null) { startClass = findMainMethodWithTimeoutWarning(source); } if (startClass == null) { throw new IllegalStateException("Unable to find main class."); } manifest.getMainAttributes().putValue(MAIN_CLASS_ATTRIBUTE, startClass); manifest.getMainAttributes().putValue(START_CLASS_ATTRIBUTE, startClass); manifest.getMainAttributes().putValue(ARK_BIZ_NAME, this.bizName); manifest.getMainAttributes().putValue(ARK_BIZ_VERSION, this.bizVersion); manifest.getMainAttributes().putValue(PRIORITY_ATTRIBUTE, priority); manifest.getMainAttributes().putValue(WEB_CONTEXT_PATH, webContextPath); manifest.getMainAttributes().putValue(DENY_IMPORT_PACKAGES, StringUtils.setToStr(denyImportPackages, MANIFEST_VALUE_SPLIT)); manifest.getMainAttributes().putValue(DENY_IMPORT_CLASSES, StringUtils.setToStr(denyImportClasses, MANIFEST_VALUE_SPLIT)); manifest.getMainAttributes().putValue(DENY_IMPORT_RESOURCES, StringUtils.setToStr(denyImportResources, MANIFEST_VALUE_SPLIT)); manifest.getMainAttributes().putValue(INJECT_PLUGIN_DEPENDENCIES, setToStr(injectPluginDependencies, MANIFEST_VALUE_SPLIT)); manifest.getMainAttributes().putValue(INJECT_EXPORT_PACKAGES, StringUtils.setToStr(injectPluginExportPackages, MANIFEST_VALUE_SPLIT)); manifest.getMainAttributes().putValue(DECLARED_LIBRARIES, StringUtils.setToStr(declaredLibraries, MANIFEST_VALUE_SPLIT)); return appendBuildInfo(manifest); } public static String setToStr(Set artifactItemSet, String delimiter) { if (artifactItemSet == null || artifactItemSet.isEmpty()) { return ""; } AssertUtils.assertNotNull(delimiter, "Delimiter should not be null."); StringBuilder sb = new StringBuilder(); for (ArtifactItem item : artifactItemSet) { sb.append(item.getArtifactId()).append("-").append(item.getVersion()).append(delimiter); } return sb.substring(0, sb.length() - delimiter.length()); } private Manifest buildAppManifest(JarFile source) throws IOException { Manifest manifest = source.getManifest(); /* theoretically impossible */ if (manifest == null) { manifest = new Manifest(); manifest.getMainAttributes().putValue("Manifest-Version", "1.0"); } manifest = new Manifest(manifest); manifest.getMainAttributes().putValue(MAIN_CLASS_ATTRIBUTE, Layouts.Jar.jar().getLauncherClassName()); manifest.getMainAttributes().putValue(START_CLASS_ATTRIBUTE, Layouts.Jar.jar().getLauncherClassName()); if (arkVersion == null || arkVersion.isEmpty()) { throw new IllegalStateException("must specify version of SOFAArk."); } manifest.getMainAttributes().putValue(ARK_VERSION_ATTRIBUTE, arkVersion); manifest.getMainAttributes().putValue(ARK_CONTAINER_ROOT, Layouts.Jar.jar().getArkContainerLocation()); return appendBuildInfo(manifest); } private Manifest appendBuildInfo(Manifest manifest) { manifest.getMainAttributes().putValue(BUILD_TIME, new SimpleDateFormat(DATE_FORMAT).format(new Date())); if (gitInfo != null) { manifest.getMainAttributes().putValue(REMOTE_ORIGIN_URL, gitInfo.getRepository()); manifest.getMainAttributes().putValue(BRANCH, gitInfo.getBranchName()); manifest.getMainAttributes().putValue(COMMIT_ID, gitInfo.getLastCommitId()); manifest.getMainAttributes().putValue(COMMIT_AUTHOR_NAME, gitInfo.getLastCommitUser()); manifest.getMainAttributes() .putValue(COMMIT_AUTHOR_EMAIL, gitInfo.getLastCommitEmail()); manifest.getMainAttributes().putValue(COMMIT_TIME, gitInfo.getLastCommitDateTime()); manifest.getMainAttributes().putValue(COMMIT_TIMESTAMP, String.valueOf(gitInfo.getLastCommitTime())); manifest.getMainAttributes().putValue(BUILD_USER, gitInfo.getBuildUser()); manifest.getMainAttributes().putValue(BUILD_EMAIL, gitInfo.getBuildEmail()); } return manifest; } public String findMainMethodWithTimeoutWarning(JarFile source) throws IOException { long startTime = System.currentTimeMillis(); String mainMethod = findMainMethod(source); long duration = System.currentTimeMillis() - startTime; if (duration > FIND_WARNING_TIMEOUT) { for (MainClassTimeoutWarningListener listener : this.mainClassTimeoutListeners) { listener.handleTimeoutWarning(duration, mainMethod); } } return mainMethod; } private String findMainMethod(JarFile source) throws IOException { return MainClassFinder .findSingleMainClass(source, null, SPRING_BOOT_APPLICATION_CLASS_NAME); } private boolean alreadyRepackaged() throws IOException { try (JarFile jarFile = new JarFile(this.source)) { Manifest manifest = jarFile.getManifest(); return (manifest != null && manifest.getMainAttributes() .getValue(ARK_VERSION_ATTRIBUTE) != null); } } public final File getModuleTargetFile() { return pluginModuleJar; } /** * Callback interface used to present a warning when finding the main class takes too * long. */ public interface MainClassTimeoutWarningListener { /** * Handle a timeout warning. * @param duration the amount of time it took to find the main method * @param mainMethod the main method that was actually found */ void handleTimeoutWarning(long duration, String mainMethod); } /** * An {@code EntryTransformer} that renames entries by applying a prefix. */ static final class RenamingEntryTransformer implements JarWriter.EntryTransformer { private final String namePrefix; RenamingEntryTransformer(String namePrefix) { this.namePrefix = namePrefix; } @Override public JarEntry transform(JarEntry entry) { JarEntry renamedEntry = new JarEntry(this.namePrefix + entry.getName()); renamedEntry.setTime(entry.getTime()); renamedEntry.setSize(entry.getSize()); renamedEntry.setMethod(entry.getMethod()); if (entry.getComment() != null) { renamedEntry.setComment(entry.getComment()); } renamedEntry.setCompressedSize(entry.getCompressedSize()); renamedEntry.setCrc(entry.getCrc()); //setCreationTimeIfPossible(entry, renamedEntry); if (entry.getExtra() != null) { renamedEntry.setExtra(entry.getExtra()); } return renamedEntry; } } public boolean isPackageProvided() { return packageProvided; } public void setPackageProvided(boolean packageProvided) { this.packageProvided = packageProvided; } public void setSkipArkExecutable(boolean skipArkExecutable) { this.skipArkExecutable = skipArkExecutable; } public void setKeepArkBizJar(boolean keepArkBizJar) { this.keepArkBizJar = keepArkBizJar; } public void setBaseDir(File baseDir) { this.baseDir = baseDir; } public void setWebContextPath(String webContextPath) { this.webContextPath = webContextPath; } public void setDeclaredMode(boolean declaredMode) { this.declaredMode = declaredMode; } public boolean isDeclaredMode() { return declaredMode; } } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/main/java/com/alipay/sofa/ark/tools/RepackagingLayout.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; /** * * @author qilong.zql * @since 0.1.0 */ public interface RepackagingLayout extends Layout { /** * Returns the location to which SOFAArk should be moved. * @return the repackaged SOFAArk location */ String getArkContainerLocation(); /** * Returns the location to which SOFAArk plugin should be moved. * @return the repackaged SOFAArk plugin location */ String getArkPluginLocation(); /** * Returns the location to which SOFA module should be moved * @return the repackaged SOFAArk module location */ String getArkModuleLocation(); } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/main/java/com/alipay/sofa/ark/tools/git/GitInfo.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools.git; public class GitInfo { private String buildUser; private String buildEmail; private String lastCommitId; private long lastCommitTime; private String lastCommitDateTime; private String lastCommitUser; private String lastCommitEmail; private String branchName; private String repository; public String getBuildUser() { return buildUser; } public void setBuildUser(String buildUser) { this.buildUser = buildUser; } public String getBuildEmail() { return buildEmail; } public void setBuildEmail(String buildEmail) { this.buildEmail = buildEmail; } public String getLastCommitId() { return lastCommitId; } public void setLastCommitId(String lastCommitId) { this.lastCommitId = lastCommitId; } public long getLastCommitTime() { return lastCommitTime; } public void setLastCommitTime(long lastCommitTime) { this.lastCommitTime = lastCommitTime; } public String getLastCommitUser() { return lastCommitUser; } public void setLastCommitUser(String lastCommitUser) { this.lastCommitUser = lastCommitUser; } public String getLastCommitEmail() { return lastCommitEmail; } public void setLastCommitEmail(String lastCommitEmail) { this.lastCommitEmail = lastCommitEmail; } public String getBranchName() { return branchName; } public void setBranchName(String branchName) { this.branchName = branchName; } public String getRepository() { return repository; } public void setRepository(String repository) { this.repository = repository; } public String getLastCommitDateTime() { return lastCommitDateTime; } public void setLastCommitDateTime(String lastCommitDateTime) { this.lastCommitDateTime = lastCommitDateTime; } @Override public String toString() { return "GitInfo{" + "buildUser='" + buildUser + '\'' + ", buildEmail='" + buildEmail + '\'' + ", lastCommitId='" + lastCommitId + '\'' + ", lastCommitTime=" + lastCommitTime + ", lastCommitDateTime='" + lastCommitDateTime + '\'' + ", lastCommitUser='" + lastCommitUser + '\'' + ", lastCommitEmail='" + lastCommitEmail + '\'' + ", branchName='" + branchName + '\'' + ", repository='" + repository + '\'' + '}'; } } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/main/java/com/alipay/sofa/ark/tools/git/JGitParser.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools.git; import org.apache.commons.lang3.StringUtils; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.ListBranchCommand; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; import static com.alipay.sofa.ark.spi.constant.Constants.DATE_FORMAT; import static org.eclipse.jgit.lib.Constants.MASTER; public class JGitParser { public static GitInfo parse(File gitDirectory) { try (FileRepository repository = getGitRepository(gitDirectory)) { GitInfo gitInfo = new GitInfo(); FileBasedConfig config = repository.getConfig(); String remoteUrl = config.getString("remote", "origin", "url"); String userName = config.getString("user", null, "name"); String userEmail = config.getString("user", null, "email"); String branchName = repository.getBranch(); gitInfo.setRepository(remoteUrl); gitInfo.setBuildUser(userName); gitInfo.setBuildEmail(userEmail); gitInfo.setBranchName(branchName); RevCommit lastCommit = getLastCommit(repository); if (lastCommit != null) { String lastCommitId = lastCommit.getId().getName(); long lastCommitTime = lastCommit.getCommitterIdent().getWhen().getTime(); String lastCommitUser = lastCommit.getAuthorIdent().getName(); String lastCommitEmail = lastCommit.getAuthorIdent().getEmailAddress(); String commitDateTime = new SimpleDateFormat(DATE_FORMAT).format(lastCommitTime); gitInfo.setLastCommitId(lastCommitId); gitInfo.setLastCommitUser(lastCommitUser); gitInfo.setLastCommitEmail(lastCommitEmail); gitInfo.setLastCommitTime(lastCommitTime); gitInfo.setLastCommitDateTime(commitDateTime); if (lastCommitId.equals(branchName)) { gitInfo.setBranchName(StringUtils.join( getBranchesFromCommit(repository, lastCommitId), ",")); } } return gitInfo; } catch (Exception exception) { return null; } } static List getBranchesFromCommit(FileRepository repository, String lastCommitId) throws GitAPIException { List refs = Git.wrap(repository).branchList() .setListMode(ListBranchCommand.ListMode.REMOTE) .setContains(lastCommitId) .call(); return refs.stream() .filter(ref -> !ref.isSymbolic()) .map(Ref::getName) .map(repository::shortenRemoteBranchName) .filter(StringUtils::isNotBlank) .distinct() .sorted(MASTER_FIRST_COMPARATOR) .collect(Collectors.toList()); } public static final Comparator MASTER_FIRST_COMPARATOR = (o1, o2) -> MASTER.equals(o1) ? -1 : 1; private static RevCommit getLastCommit(Repository repository) throws Exception { RevWalk revWalk = new RevWalk(repository); Ref headCommitReference = repository.getRefDatabase().findRef("HEAD"); ObjectId headObjectId; if (headCommitReference != null) { headObjectId = headCommitReference.getObjectId(); } else { headObjectId = repository.resolve("HEAD"); } if (headObjectId == null) { throw new Exception( "Could not get HEAD Ref, are you sure have commits in the gitDirectory?"); } RevCommit headCommit = revWalk.parseCommit(headObjectId); revWalk.markStart(headCommit); return headCommit; } private static FileRepository getGitRepository(File gitDirectory) throws Exception { if (gitDirectory == null || !gitDirectory.exists()) { throw new Exception("Could not create git repository. " + gitDirectory + " is not exists!"); } Repository repository; try { repository = new FileRepositoryBuilder().setGitDir(gitDirectory).readEnvironment() // scan environment GIT_* variables .findGitDir() // scan up the file system tree .build(); } catch (IOException e) { throw new Exception("Could not initialize git repository...", e); } if (repository == null) { throw new Exception("Could not create git repository. Are you sure '" + gitDirectory + "' is the valid Git root for your project?"); } return (FileRepository) repository; } } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/test/java/com/alipay/sofa/ark/tools/ArtifactItemTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * @author lianglipeng.llp@alibaba-inc.com * @version $Id: ArtifactItemTest.java, v 0.1 2024年08月07日 16:54 立蓬 Exp $ */ public class ArtifactItemTest { @Test public void testParseArtifactItem() { // case1: {groupId:artifactId} ArtifactItem item = ArtifactItem.parseArtifactItem("com.alipay.sofa:sofa-ark-plugin"); assertEquals("com.alipay.sofa", item.getGroupId()); assertEquals("sofa-ark-plugin", item.getArtifactId()); // case2: {groupId:artifactId:version} item = ArtifactItem.parseArtifactItem("com.alipay.sofa:sofa-ark-plugin:1.0.0"); assertEquals("com.alipay.sofa", item.getGroupId()); assertEquals("sofa-ark-plugin", item.getArtifactId()); assertEquals("1.0.0", item.getVersion()); // case3: {groupId:artifactId:version:classifier} item = ArtifactItem.parseArtifactItem("com.alipay.sofa:sofa-ark-plugin:1.0.0:jdk11"); assertEquals("com.alipay.sofa", item.getGroupId()); assertEquals("sofa-ark-plugin", item.getArtifactId()); assertEquals("1.0.0", item.getVersion()); assertEquals("jdk11", item.getClassifier()); } @Test public void testIsSameWithVersion() { // case1: dependencyWithoutClassifier ArtifactItem dependencyWithoutClassifier = ArtifactItem .parseArtifactItem("com.alipay.sofa:sofa-ark-plugin:1.0.0"); ArtifactItem item = ArtifactItem.parseArtifactItem("com.alipay.sofa:sofa-ark-plugin"); assertTrue(item.isSameWithVersion(dependencyWithoutClassifier)); item = ArtifactItem.parseArtifactItem("com.alipay.sofa:sofa-ark-plugin:1.0.0"); assertTrue(item.isSameWithVersion(dependencyWithoutClassifier)); item = ArtifactItem.parseArtifactItem("com.alipay.sofa:sofa-ark-plugin:1.0.1"); assertFalse(item.isSameWithVersion(dependencyWithoutClassifier)); // case2: dependencyWithClassifier ArtifactItem dependencyWithClassifier = ArtifactItem .parseArtifactItem("com.alipay.sofa:sofa-ark-plugin:1.0.0:jdk11"); item = ArtifactItem.parseArtifactItem("com.alipay.sofa:sofa-ark-plugin:1.0.0:jdk11"); assertTrue(item.isSameWithVersion(dependencyWithClassifier)); item = ArtifactItem.parseArtifactItem("com.alipay.sofa:sofa-ark-plugin:1.0.1:jdk11"); assertFalse(item.isSameWithVersion(dependencyWithClassifier)); item = ArtifactItem.parseArtifactItem("com.alipay.sofa:sofa-ark-plugin:1.0.0:jdk12"); assertFalse(item.isSameWithVersion(dependencyWithClassifier)); item = ArtifactItem.parseArtifactItem("com.alipay.sofa:sofa-ark-plugin:1.0.0"); assertFalse(item.isSameWithVersion(dependencyWithClassifier)); item = ArtifactItem.parseArtifactItem("com.alipay.sofa:sofa-ark-plugin"); assertFalse(item.isSameWithVersion(dependencyWithClassifier)); } } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/test/java/com/alipay/sofa/ark/tools/JarWriterTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; import com.alipay.sofa.ark.tools.JarWriter.ZipHeaderPeekInputStream; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.jar.JarFile; import java.util.jar.JarInputStream; import java.util.jar.Manifest; import static com.alipay.sofa.ark.tools.LibraryScope.MODULE; import static org.junit.Assert.assertEquals; /** * @author lylingzhen * @since 2.2.0 */ public class JarWriterTest { private JarWriter jarWriter; private File file = new File("./JarWriterTest"); private String jarFilePath = this.getClass().getClassLoader().getResource("test-jar.jar") .getFile(); @Before public void setUp() throws IOException { file.createNewFile(); jarWriter = new JarWriter(file); } @After public void tearDown() { file.delete(); } @Test public void testWriteManifest() throws Exception { Manifest manifest = new Manifest(); jarWriter.writeManifest(manifest); } @Test public void testWriteMethods() throws IOException { jarWriter.writeMarkEntry(); JarFile jarFile = new JarFile(jarFilePath); jarWriter.writeBootstrapEntry(jarFile); Library library = new Library(new File(jarFilePath), MODULE); jarWriter.writeNestedLibrary("./", library); jarWriter.writeLoaderClasses(new JarInputStream(new FileInputStream(jarFilePath))); ZipHeaderPeekInputStream zipHeaderPeekInputStream = new ZipHeaderPeekInputStream( new FileInputStream(jarFilePath)); assertEquals(80, zipHeaderPeekInputStream.read()); } } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/test/java/com/alipay/sofa/ark/tools/LayoutsTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; import com.alipay.sofa.ark.tools.Layouts.Jar; import com.alipay.sofa.ark.tools.Layouts.Module; import org.junit.Test; import static com.alipay.sofa.ark.tools.Layouts.Jar.jar; import static com.alipay.sofa.ark.tools.Layouts.Module.module; import static com.alipay.sofa.ark.tools.LibraryScope.*; import static org.junit.Assert.assertEquals; /** * @author lylingzhen * @since 2.2.0 */ public class LayoutsTest { @Test public void testLayouts() throws Exception { Jar jar = jar(); assertEquals("SOFA-ARK/plugin/", jar.getLibraryDestination(null, PLUGIN)); assertEquals("SOFA-ARK/biz/", jar.getLibraryDestination(null, MODULE)); assertEquals("SOFA-ARK/container/", jar.getLibraryDestination(null, CONTAINER)); assertEquals("", jar.getLibraryDestination(null, PROVIDED)); assertEquals("com.alipay.sofa.ark.bootstrap.ArkLauncher", jar.getLauncherClassName()); assertEquals(true, jar.isExecutable()); assertEquals(true, jar.isExecutable()); Module module = module(); assertEquals("", module.getLauncherClassName()); assertEquals("lib/", module.getLibraryDestination(null, null)); assertEquals(false, module.isExecutable()); } } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/test/java/com/alipay/sofa/ark/tools/MainClassFinderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.util.jar.JarFile; import static com.alipay.sofa.ark.tools.MainClassFinder.findSingleMainClass; import static org.junit.Assert.assertEquals; public class MainClassFinderTest { private String jarFilePath = this.getClass().getClassLoader().getResource("test-jar.jar") .getFile(); private JarFile jarFile; @Before public void setUp() throws IOException { jarFile = new JarFile(jarFilePath); } @Test public void testFindSingleMainClass() throws IOException { assertEquals("com.alipay.sofa.ark.sample.springbootdemo.SpringbootDemoApplication", findSingleMainClass(jarFile, "", "")); } } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/test/java/com/alipay/sofa/ark/tools/RepackagerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools; import com.alipay.sofa.ark.tools.Repackager.RenamingEntryTransformer; import com.alipay.sofa.ark.tools.git.GitInfo; import org.junit.Before; import org.junit.Test; import java.io.File; import java.lang.reflect.Field; import java.net.URL; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; import static com.alipay.sofa.ark.tools.LibraryScope.*; import static com.alipay.sofa.ark.tools.Repackager.isZip; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Sets.newHashSet; import static java.util.zip.ZipOutputStream.STORED; import static org.junit.Assert.*; /** * @author qilong.zql * @since 0.6.0 */ public class RepackagerTest { private Repackager repackager; private String jarFilePath = this.getClass().getClassLoader().getResource("test-jar.jar") .getFile(); private File jarFile = new File(jarFilePath); @Before public void setUp() { repackager = new Repackager(jarFile); } @Test public void testZipFile() { URL testJarUrl = this.getClass().getClassLoader().getResource("test-jar.jar"); URL testPomUrl = this.getClass().getClassLoader().getResource("test-pom.xml"); assertNotNull(testJarUrl); assertNotNull(testPomUrl); assertTrue(isZip(new File(testJarUrl.getFile()))); assertFalse(isZip(new File(testPomUrl.getFile()))); } @Test(expected = IllegalArgumentException.class) public void testConstructWithNullFile() throws IllegalArgumentException { new Repackager(null); } @Test(expected = IllegalArgumentException.class) public void testConstructWithFileNotExists() throws IllegalArgumentException { new Repackager(new File("not_found")); } @Test public void testRepackage() throws Exception { Field field = Repackager.class.getDeclaredField("arkContainerLibrary"); field.setAccessible(true); field.set(repackager, new Library("sofa-ark-2.0.jar", jarFile, CONTAINER, true)); repackager.setArkVersion("2.0"); repackager.setBaseDir(new File(this.getClass().getClassLoader().getResource("").getFile())); LinkedHashSet linkedHashSet = new LinkedHashSet<>(); linkedHashSet.add("sofa-ark:2.0"); repackager.setInjectPluginDependencies(linkedHashSet); field = Repackager.class.getDeclaredField("gitInfo"); field.setAccessible(true); field.set(repackager, new GitInfo()); Library library = new Library(jarFile, PLUGIN); repackager.repackage(new File("./target/dest"), new File("./target/module"), callback -> { callback.library(library); }); field = Repackager.class.getDeclaredField("arkModuleLibraries"); field.setAccessible(true); assertEquals(newArrayList(library), field.get(repackager)); assertEquals(MODULE, ((List) field.get(repackager)).get(0).getScope()); assertEquals("com.alipay.sofa.ark.sample.springbootdemo.SpringbootDemoApplication", repackager.findMainMethodWithTimeoutWarning(new JarFile(jarFile))); } @Test public void testSetInjectPluginDependencies() throws Exception { repackager.setInjectPluginDependencies(null); LinkedHashSet linkedHashSet = new LinkedHashSet<>(); linkedHashSet.add("sofa-ark:1.0"); repackager.setInjectPluginDependencies(linkedHashSet); Field field = Repackager.class.getDeclaredField("injectPluginDependencies"); field.setAccessible(true); ArtifactItem item = new ArtifactItem(); item.setArtifactId("sofa-ark"); item.setVersion("1.0"); assertEquals(newHashSet(item), field.get(repackager)); } @Test public void testPrepareDeclaredLibraries() throws Exception { repackager.prepareDeclaredLibraries(null); repackager.setDeclaredMode(true); repackager.prepareDeclaredLibraries(null); ArtifactItem artifactItem = new ArtifactItem(); artifactItem.setArtifactId("x"); repackager.prepareDeclaredLibraries(newArrayList(artifactItem)); Field field = Repackager.class.getDeclaredField("declaredLibraries"); field.setAccessible(true); assertEquals(newHashSet("x"), field.get(repackager)); } @Test public void testRenamingEntryTransformer() { RenamingEntryTransformer renamingEntryTransformer = new RenamingEntryTransformer( "my-prefix"); JarEntry jarEntry = new JarEntry("my-entry"); jarEntry.setComment("xxx"); jarEntry.setExtra(new byte[] {}); jarEntry.setSize(1); jarEntry.setMethod(STORED); jarEntry.setCrc(1); jarEntry = renamingEntryTransformer.transform(jarEntry); assertEquals("my-prefixmy-entry", jarEntry.getName()); } @Test public void testOtherMethods() { repackager.addMainClassTimeoutWarningListener(null); repackager.setMainClass(null); repackager.setBizName(null); repackager.setBizVersion(null); repackager.setPriority(null); repackager.setDenyImportPackages(null); repackager.setDenyImportClasses(null); repackager.setDenyImportResources(null); repackager.setInjectPluginExportPackages(null); } } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/test/java/com/alipay/sofa/ark/tools/git/JGitParserTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.tools.git; import com.alipay.sofa.ark.tools.Repackager; import org.eclipse.jgit.internal.storage.file.FileRepository; import org.junit.Test; import java.io.File; import static com.alipay.sofa.ark.tools.git.JGitParser.getBranchesFromCommit; import static com.alipay.sofa.ark.tools.git.JGitParser.parse; import static org.junit.Assert.*; public class JGitParserTest { @Test public void testParse() { File gitFile = new File("../../../.git"); GitInfo gitInfo = parse(gitFile); assertNotNull(gitInfo); gitInfo.getBuildUser(); gitInfo.getBuildEmail(); assertNotNull(gitInfo.getLastCommitId()); assertNotEquals(gitInfo.getLastCommitTime(), 0L); assertNotNull(gitInfo.getLastCommitDateTime()); assertNotNull(gitInfo.getLastCommitUser()); assertNotNull(gitInfo.getLastCommitEmail()); assertNotNull(gitInfo.getBranchName()); assertNotNull(gitInfo.getRepository()); assertNotNull(gitInfo.toString()); Repackager repackager = new Repackager(new File("../../../pom.xml")); repackager.setGitDirectory(gitFile); } @Test public void testGetBranchesFromCommit() { try { FileRepository fileRepository = new FileRepository("../../../.git"); assertEquals("master", getBranchesFromCommit(fileRepository, "3bb887feb99475b7d6bb40f926aa734fbe62e0f6") .get(0)); } catch (Throwable e) { } } } ================================================ FILE: sofa-ark-parent/support/ark-tools/src/test/resources/test-pom.xml ================================================ 4.0.0 for-test com.alipay.sofa 0.0.1 ================================================ FILE: sofa-ark-parent/support/pom.xml ================================================ sofa-ark-parent com.alipay.sofa ${sofa.ark.version} 4.0.0 sofa-ark-support ${project.groupId}:${project.artifactId} pom ark-plugin-maven-plugin ark-maven-plugin ark-support-starter ark-tools ark-springboot-integration/ark-springboot-starter ark-springboot-integration/ark-compatible-springboot1 ark-springboot-integration/ark-compatible-springboot2 ark-springboot-integration/ark-common-springboot ================================================ FILE: sofa-ark-plugin/config-ark-plugin/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `config-ark-plugin` **Package**: `com.alipay.sofa.ark.config` This is a built-in Ark Plugin that provides dynamic configuration management for business modules using ZooKeeper or Apollo. ## Purpose - Receive dynamic configuration from configuration centers - Control dynamic Biz install/uninstall based on configuration changes - Support ZooKeeper and Apollo as configuration sources ## Key Classes ### `ConfigBaseActivator` Plugin activator that initializes configuration listeners. ### Configuration Listeners - Listen for configuration changes from ZK or Apollo - Parse configuration to determine Biz operations - Trigger install/uninstall/switch operations ## Configuration Format The plugin expects configuration in this format: ```yaml bizs: - name: my-biz version: 1.0.0 url: http://example.com/my-biz-1.0.0.jar operation: INSTALL ``` ## Supported Operations - `INSTALL` - Install a new Biz - `UNINSTALL` - Uninstall a Biz - `SWITCH` - Switch active Biz version ## Usage Add plugin dependency: ```xml com.alipay.sofa config-ark-plugin ark-plugin ``` Configure in `conf/ark/bootstrap.yml`: ```yaml ark: config: zk: server: localhost:2181 path: /sofa-ark/config ``` ## Dependencies - `sofa-ark-spi` - Service interfaces - `sofa-ark-api` - API for Biz operations - `curator-recipes` - ZooKeeper client - `apollo-client` - Apollo client (provided scope) ## Used By - Applications requiring dynamic Biz management via configuration center ================================================ FILE: sofa-ark-plugin/config-ark-plugin/DEMO_SHOW.md ================================================ # 通过Apollo管理SOFAArk模块安装 ## 项目准备 #### apollo-quick-start 使用[sofa-ark-dynamic-guides](https://github.com/apolloconfig/apollo-quick-start)项目 来创建apollo本地配置中心 #### sofa-ark-dynamic-guides 使用[sofa-ark-dynamic-guides](https://github.com/sofastack-guides/sofa-ark-dynamic-guides)作为演示模块动态安装的目标业务项目 ## 配置与准备 ### sofa-ark-dynamic-guides项目配置 - 修改dynamic-stock-mng中application-properties ``` #8080和apollo的config service服务端口冲突 server.port=8081 #新增apollo客户端配置 app.id=SampleApp apollo.meta=http://localhost:8080 ``` - 参考 [SOFAArk 配置](https://www.sofastack.tech/projects/sofa-boot/sofa-ark-ark-config/),在 dynamic-stock-mng目录下新增文件 `${baseDir}/conf/ark/bootstrap.properties`;并增加如下配置,设置使用apollo作为配置中心 ``` sofa.ark.config.server.type=apollo ``` - 新增config-ark-plugin项目的pom.xml依赖 ``` com.alipay.sofa config-ark-plugin ${sofa.ark.version} com.ctrip.framework.apollo apollo-client 2.1.0 ``` - 增加dynamic-stock-mng的pom.xml依赖 ``` com.alipay.sofa config-ark-plugin com.ctrip.framework.apollo apollo-client ``` ### apollo后台启动配置 - 按照文档apollo-quick-start文档启动apollo ``` zsk@MC267220 apollo-quick-start % ./demo.sh start ==== starting service ==== Service logging file is ./service/apollo-service.log Started [1225] Waiting for config service startup...... Config service started. You may visit http://localhost:8080 for service status now! Waiting for admin service startup. Admin service started ==== starting portal ==== Portal logging file is ./portal/apollo-portal.log Started [1343] Waiting for portal startup.... Portal started. You can visit http://localhost:8070 now! ``` - 需要额外在Apollo后台为应用独立创建一个管理SOFAArk的Namespace - Namespace的名称为sofa-ark - 创建key-value来管理SOFAArk 的模块加载, - key的名字必须为masterBiz, 类型为String - value初始值为空 ## 验证 ### 启动StockMngApplication基座 参考 [启动宿主应用 ](https://github.com/sofastack-guides/sofa-ark-dynamic-guides#4%E6%89%93%E5%8C%85--%E5%90%AF%E5%8A%A8%E5%AE%BF%E4%B8%BB%E5%BA%94%E7%94%A8) ### 连接 SOFAArk telnet ``` ## 连接 SOFAArk telnet > telnet localhost 1234 ## 查看安装的模块信息 sofa-ark>biz -a stock-mng:1.0.0:activated biz count = 1 ``` 此时访问[http://localhost:8081/](http://localhost:8081/) 报错 ![image.png](images/err_page.png) ### 通过apollo后台安装biz模块 #### 安装dynamic-provider模块 为上述Namespace为ark-biz 中的masterBiz设置一下值,然后进行发布。 ![image.png](images/master_biz.png) ### 验证结果 #### 检查日志 找到logs/stock-mng/sofa-ark/config-message.log ![image.png](images/message_log.png) 可以看到apollo后台推送的指令已经被执行 #### biz模块检查 再次允许biz -a, 确认dynamic-provider模块已经安装成功 ``` sofa-ark>biz -a dynamic-provider:2.0.0:activated stock-mng:1.0.0:activated biz count = 2 ``` #### 访问服务[http://localhost:8081/](http://localhost:8081/) 再次刷新页面,页面现在可以正常访问。至此,整个演示结束。 ![image.png](images/ok_page.png) ================================================ FILE: sofa-ark-plugin/config-ark-plugin/README.md ================================================ # Apollo 配置 在介绍 [Biz 生命周期](https://www.sofastack.tech/projects/sofa-boot/sofa-ark-ark-config/) 时,我们提到了有三种方式控制 Biz 的生命周期,并且介绍了使用客户端 API 实现 Biz 的安装、卸载、激活。在这一章节我们介绍如何使用 SOFAArk 提供的动态配置插件,通过 Apollo 下发指令,控制 Biz 的生命周期。 另外,在[通过Apollo管理SOFAArk模块安装示例](DEMO_SHOW.md) 中演示了如何使用Apollo配置中心来动态管理ark模块的配置。 ### 引入依赖 SOFAArk 提供了 config-ark-plugin 对接 Apollo 配置中心,用于运行时接受配置,达到控制 Biz 生命周期,引入如下依赖: ```xml com.alipay.sofa config-ark-plugin ${sofa.ark.version} com.ctrip.framework.apollo apollo-client 2.1.0 ``` ### 配置 参考 [SOFAArk 配置](https://www.sofastack.tech/projects/sofa-boot/sofa-ark-ark-config/),在 SOFAArk 配置文件 `conf/ark/bootstrap.properties` 增加如下配置: ```text sofa.ark.config.server.type=apollo ``` 如果没有配置,默认使用Zookeeper作为配置中心,当然也可以通过设置sofa.ark.config.server.type=zookeeper来显式指定配置中心类型为Zookeeper ### Apollo配置维度 + 需要额外在Apollo后台为应用独立创建一个管理SOFAArk的Namespace,Namespace的名称为sofa-ark + 创建key-value来管理 SOFAArk 的模块加载, key的名字必须为masterBiz, 类型为String ### 配置value的值 下面介绍配置的形式,动态配置采用状态声明指令,SOFAArk 收到配置后,会根据状态描述解析出具体的指令(包括 install,uninstall, switch),指令格式如下: `bizName:bizVersion:bizState?k1=v1&k2=v2` 多条指令使用 `;` 隔开,单条指令主要由 biz 名称,biz 版本,biz 预期状态及参数组成。简单记住一点,状态配置是描述指令推送之后,所有非宿主 Biz 的状态; 例如当前 SOFAArk 容器部署了两个应用 A,B,版本均为 1.0,其中 A 应用为宿主应用,因为宿主应用不可卸载,因此不需要考虑宿主应用,可以简单认为当前容器的 Biz 状态声明为: > `B:1.0:Activated` 如果此时你希望安装 C 应用,版本为 1.0,文件流地址为 urlC,那么推送指令应为: > `B:1.0:Activated;C:1.0:Activated?bizUrl=urlC` 操作继续,如果你又希望安装安装 B 应用,版本为 2.0,文件流地址为 urlB,且希望 2.0 版本处于激活状态,那么你推送的指令应为: > `B:1.0:Deactivated;B:2.0:Actaivated?bizUrl=urlB;C:1.0:Activated` > 解释下为什么是这样配置指令,因为 SOFAArk 只允许应用一个版本处于激活状态,如果存在其他版本,则应处于非激活状态;所以当希望激活 B 应用 2.0 版本时,B 应用 1.0 版本应该声明为非激活状态。另外你可能注意到了 C 应用参数 urlC 不用声明了,原因是目前只有当安装新 Biz 时,才有可能需要配置参数 bizUrl,用于指定 biz 文件流地址,其他场景下,参数的解析没有意义。 操作继续,如果你希望卸载 B 应用 2.0 版本,激活 B 应用 1.0 版本,卸载 C 应用,那么推送的指令声明为: > `B:1.0:Activated` 从上面的操作描述看,在推送动态配置时,只需要声明期望的 Biz 状态即可,SOFAArk 会根据状态声明推断具体的执行指令,并尽可能保持服务的连续性,以上面最后一步操作为例,SOFAArk 推断的执行指令顺序如下: + 执行 switch 指令,激活 B 应用 1.0 版本,钝化 B 应用 2.0 版本,保证服务连续性 + 执行 uninstall 指令,卸载 B 应用 2.0 版本 + 执行 uninstall 指令,卸载 C 应用 1.0 版本 ================================================ FILE: sofa-ark-plugin/config-ark-plugin/pom.xml ================================================ sofa-ark-bom com.alipay.sofa ${sofa.ark.version} ../../sofa-ark-bom 4.0.0 config-ark-plugin ${project.groupId}:${project.artifactId} 5.1.0 com.ctrip.framework.apollo apollo-client 2.1.0 provided org.apache.curator curator-recipes ${curator.version} org.apache.curator curator-test ${curator.version} test com.alipay.sofa sofa-ark-spi provided com.alipay.sofa sofa-ark-api provided com.alipay.sofa log-sofa-boot-starter provided org.springframework.boot spring-boot com.alipay.sofa sofa-ark-common com.google.inject guice junit junit test org.mockito mockito-core test org.mockito mockito-inline test com.alipay.sofa sofa-ark-plugin-maven-plugin ${project.version} default-cli ark-plugin com.alipay.sofa.ark.config.ConfigBaseActivator *:*:* com.alipay.sofa:config-ark-plugin org.apache.maven.plugins maven-surefire-plugin org.apache.maven.surefire surefire-junit47 ${surefire.version} ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/main/java/com/alipay/sofa/ark/config/ConfigBaseActivator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.common.log.ArkLogger; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.EnvironmentUtils; import com.alipay.sofa.ark.config.apollo.ApolloConfigActivator; import com.alipay.sofa.ark.config.zk.ZookeeperConfigActivator; import com.alipay.sofa.ark.spi.model.PluginContext; import com.alipay.sofa.ark.spi.service.PluginActivator; import com.google.common.collect.ImmutableMap; import java.util.Map; import static com.alipay.sofa.ark.spi.constant.Constants.*; /** * @author zsk * @version $Id: ConfigBaseActivator.java, v 0.1 2023年09月28日 16:56 zsk Exp $ */ public class ConfigBaseActivator implements PluginActivator { private final static ArkLogger LOGGER = ArkLoggerFactory .getLogger(ConfigBaseActivator.class); private boolean enableConfigServer = EnvironmentUtils .getProperty( CONFIG_SERVER_ENABLE, "true") .equalsIgnoreCase( "true"); private String configCenterType = ArkConfigs .getStringValue(CONFIG_SERVER_TYPE); private Map> configTypeMap = ImmutableMap .of(ConfigTypeEnum.zookeeper, new LazyActivatorWrapper( ZookeeperConfigActivator.class), ConfigTypeEnum.apollo, new LazyActivatorWrapper( ApolloConfigActivator.class)); protected PluginActivator getConfigActivator() { ConfigTypeEnum configType = ConfigTypeEnum.getByNameWithDefault(configCenterType, ConfigTypeEnum.zookeeper); return configTypeMap.get(configType).getLazyActivator(); } @Override public void start(PluginContext context) { if (!enableConfigServer) { LOGGER.warn("config server is disabled."); return; } getConfigActivator().start(context); } @Override public void stop(PluginContext context) { if (!enableConfigServer) { return; } getConfigActivator().stop(context); } } ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/main/java/com/alipay/sofa/ark/config/ConfigProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config; import com.alipay.sofa.ark.common.log.ArkLogger; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.thread.CommonThreadPool; import com.alipay.sofa.ark.config.util.OperationTransformer; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.model.PluginContext; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import java.util.Deque; /** * @author qilong.zql * @since 0.6.0 */ public class ConfigProcessor { private final static ArkLogger LOGGER = ArkLoggerFactory.getLogger(ConfigProcessor.class); private Deque configDeque; private CommonThreadPool commonThreadPool; private PluginContext pluginContext; public static ConfigProcessor createConfigProcessor(PluginContext pluginContext, Deque deque, String processorName) { return new ConfigProcessor(pluginContext, deque, processorName); } public ConfigProcessor(PluginContext pluginContext, Deque configDeque, String processorName) { this.pluginContext = pluginContext; this.configDeque = configDeque; this.commonThreadPool = new CommonThreadPool().setThreadPoolName(processorName) .setCorePoolSize(1).setMaximumPoolSize(1).setDaemon(true); } public void start() { commonThreadPool.getExecutor().execute(new ConfigTask()); } public boolean isReadyProcessConfig() { BizManagerService bizManagerService = pluginContext.referenceService( BizManagerService.class).getService(); for (Biz biz : bizManagerService.getBizInOrder()) { if (biz.getBizState() != BizState.ACTIVATED && biz.getBizState() != BizState.DEACTIVATED) { return false; } } return true; } class ConfigTask implements Runnable { @Override public void run() { while (true) { if (!isReadyProcessConfig()) { sleep(200); continue; } String config = configDeque.poll(); if (config == null) { sleep(200); continue; } try { LOGGER.info("ConfigTask: {} start to process config: {}", commonThreadPool.getThreadPoolName(), config); OperationProcessor.process(OperationTransformer.transformToBizOperation(config, pluginContext)); } catch (Throwable throwable) { LOGGER.error(String.format("ConfigTask: %s failed to process config: %s", commonThreadPool.getThreadPoolName(), config), throwable); } } } private void sleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { // ignore } } } } ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/main/java/com/alipay/sofa/ark/config/ConfigTypeEnum.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config; /** * @author zsk * @version $Id: ConfigTypeEnum.java, v 0.1 2023年09月28日 17:16 zsk Exp $ */ public enum ConfigTypeEnum { zookeeper, apollo, ; public static ConfigTypeEnum getByNameWithDefault(String name, ConfigTypeEnum defaultValue) { for (ConfigTypeEnum configTypeEnum : ConfigTypeEnum.values()) { if (configTypeEnum.name().equalsIgnoreCase(name)) { return configTypeEnum; } } return defaultValue; } } ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/main/java/com/alipay/sofa/ark/config/LazyActivatorWrapper.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.service.PluginActivator; /** * @author zsk * @version $Id: lazyInitActivator.java, v 0.1 2023年10月11日 17:57 zsk Exp $ */ public class LazyActivatorWrapper { /** * activator类型 */ private final Class activatorClass; /** * 延时产生的实例 */ private volatile A lazyActivator; public LazyActivatorWrapper(Class activatorClass) { this.activatorClass = activatorClass; } public A getLazyActivator() { if (null != lazyActivator) { return lazyActivator; } synchronized (this) { if (null == lazyActivator) { try { lazyActivator = activatorClass.newInstance(); } catch (InstantiationException instantE) { throw new ArkRuntimeException( "InstantiationException in create plugin activator " + activatorClass.getName(), instantE); } catch (IllegalAccessException illegalE) { throw new ArkRuntimeException( "IllegalAccessException in create plugin activator " + activatorClass.getName(), illegalE); } catch (Throwable e) { throw e; } } } return lazyActivator; } } ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/main/java/com/alipay/sofa/ark/config/OperationProcessor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.api.ClientResponse; import com.alipay.sofa.ark.common.log.ArkLogger; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.model.BizOperation; import java.util.ArrayList; import java.util.List; /** * @author qilong.zq * @since 0.6.0 */ public class OperationProcessor { private final static ArkLogger LOGGER = ArkLoggerFactory.getLogger(OperationProcessor.class); public static List process(List bizOperations) { List clientResponses = new ArrayList<>(); try { for (BizOperation bizOperation : bizOperations) { LOGGER.info("Execute biz operation: {} {}:{}", bizOperation.getOperationType() .name(), bizOperation.getBizName(), bizOperation.getBizVersion()); switch (bizOperation.getOperationType()) { case INSTALL: clientResponses.add(ArkClient.installOperation(bizOperation)); break; case UNINSTALL: clientResponses.add(ArkClient.uninstallOperation(bizOperation)); break; case SWITCH: clientResponses.add(ArkClient.switchOperation(bizOperation)); break; case CHECK: clientResponses.add(ArkClient.checkOperation(bizOperation)); break; default: throw new ArkRuntimeException(String.format("Don't support operation: %s.", bizOperation.getOperationType())); } } } catch (Throwable throwable) { throw new ArkRuntimeException("Failed to execute biz operations.", throwable); } return clientResponses; } } ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/main/java/com/alipay/sofa/ark/config/RegistryConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.spi.constant.Constants; import java.util.Map; /** * @author qilong.zql * @since 0.6.0 */ public class RegistryConfig { /** * protocol */ private String protocol; /** * config server address */ private String address; /** * config parameters */ private Map parameters; public String getProtocol() { return protocol; } public RegistryConfig setProtocol(String protocol) { this.protocol = protocol; return this; } public String getAddress() { return address; } public RegistryConfig setAddress(String address) { this.address = address; return this; } public Map getParameters() { return parameters; } public RegistryConfig setParameters(Map parameters) { this.parameters = parameters; return this; } public String getParameter(String key) { return getParameters().get(key); } public String getParameter(String key, String defaultValue) { String value = getParameter(key); return value == null ? defaultValue : value; } public int getConnectTimeout() { return ArkConfigs.getIntValue(Constants.CONFIG_CONNECT_TIMEOUT, Constants.DEFAULT_CONFIG_CONNECT_TIMEOUT); } } ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/main/java/com/alipay/sofa/ark/config/apollo/ApolloConfigActivator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config.apollo; import com.alipay.sofa.ark.common.log.ArkLogger; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.config.OperationProcessor; import com.alipay.sofa.ark.config.util.OperationTransformer; import com.alipay.sofa.ark.spi.model.PluginContext; import com.alipay.sofa.ark.spi.service.PluginActivator; import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.ConfigChangeListener; import com.ctrip.framework.apollo.ConfigService; import com.ctrip.framework.apollo.model.ConfigChange; import com.ctrip.framework.apollo.model.ConfigChangeEvent; import static com.alipay.sofa.ark.spi.constant.Constants.APOLLO_MASTER_BIZ_KEY; import static com.alipay.sofa.ark.spi.constant.Constants.CONFIG_APOLLO_NAMESPACE; /** * @author zsk * @version $Id: ApolloConfigActivator.java, v 0.1 2023年09月28日 17:24 zsk Exp $ */ public class ApolloConfigActivator implements PluginActivator { private final static ArkLogger LOGGER = ArkLoggerFactory.getLogger(ApolloConfigActivator.class); private ConfigChangeListener changeListener; @Override public void start(PluginContext context) { LOGGER.info("start apollo config activator"); Config config = ConfigService.getConfig(CONFIG_APOLLO_NAMESPACE); changeListener = new ConfigChangeListener() { @Override public void onChange(ConfigChangeEvent changeEvent) { for (String key : changeEvent.changedKeys()) { if (APOLLO_MASTER_BIZ_KEY.equals(key)) { ConfigChange change = changeEvent.getChange(key); String value = change.getNewValue(); if (StringUtils.isEmpty(value)) { LOGGER.info("ignore empty masterBiz value"); return; } LOGGER.info("Start to process masterBiz config: {}", value); OperationProcessor.process(OperationTransformer.transformToBizOperation( value, context)); } else { LOGGER .warn( "only accept config key={} in nameSpace={}, ignore changeEvent of key={}", APOLLO_MASTER_BIZ_KEY, CONFIG_APOLLO_NAMESPACE, key); } } } }; config.addChangeListener(changeListener); } @Override public void stop(PluginContext context) { LOGGER.info("stop apollo config activator"); Config config = ConfigService.getConfig(CONFIG_APOLLO_NAMESPACE); if (null == changeListener || null == config) { return; } config.removeChangeListener(changeListener); } } ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/main/java/com/alipay/sofa/ark/config/util/NetUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config.util; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.exception.ArkRuntimeException; import java.net.InetAddress; /** * @author qilong.zql * @since 0.6.0 */ public class NetUtils { private static String localhost; public static String getLocalHostAddress() { try { if (StringUtils.isEmpty(localhost)) { localhost = InetAddress.getLocalHost().getHostAddress(); } return localhost; } catch (Throwable throwable) { throw new ArkRuntimeException(throwable); } } } ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/main/java/com/alipay/sofa/ark/config/util/OperationTransformer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config.util; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizOperation; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.model.PluginContext; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author qilong.zql * @since 0.6.0 */ public class OperationTransformer { /** * transform config into biz operation * * @param config * @return */ public static List transformToBizOperation(String config, PluginContext pluginContext) throws IllegalStateException { BizManagerService bizManagerService = pluginContext.referenceService( BizManagerService.class).getService(); Map> currentBizState = new HashMap<>(); for (Biz biz : bizManagerService.getBizInOrder()) { if (!BizState.ACTIVATED.equals(biz.getBizState()) && !BizState.DEACTIVATED.equals(biz.getBizState())) { throw new IllegalStateException(String.format( "Exist illegal biz: %s, please wait.", biz)); } if (biz.getBizName().equals(ArkConfigs.getStringValue(Constants.MASTER_BIZ))) { continue; } else if (currentBizState.get(biz.getBizName()) == null) { currentBizState.put(biz.getBizName(), new HashMap()); } currentBizState.get(biz.getBizName()).put(biz.getBizVersion(), biz.getBizState()); } return doTransformToBizOperation(config, currentBizState); } public static List doTransformToBizOperation(String config, Map> currentBizState) throws IllegalStateException { List bizOperations = new ArrayList<>(); Map> expectedBizState = new HashMap<>(); Map> progressBizState = cloneBizStateMap(currentBizState); ArrayList configOperation = adjustOperationOrder(config); for (String operation : configOperation) { int idx = operation.indexOf(Constants.QUESTION_MARK_SPLIT); String[] configInfo = (idx == -1) ? operation.split(Constants.STRING_COLON) : operation .substring(0, idx).split(Constants.STRING_COLON); String bizName = configInfo[0]; String bizVersion = configInfo[1]; String stateStr = configInfo[2]; String parameterStr = (idx == -1) ? Constants.EMPTY_STR : operation.substring(idx + 1); BizState bizState = BizState.of(stateStr); Map parameters = parseParameter(parameterStr); if (expectedBizState.get(bizName) != null && expectedBizState.get(bizName).get(bizVersion) == bizState) { continue; } else if (expectedBizState.get(bizName) != null && expectedBizState.get(bizName).get(bizVersion) != null && expectedBizState.get(bizName).get(bizVersion) != bizState) { throw new IllegalStateException(String.format( "Don't specify same biz with different bizState, config is %s.", config)); } else if (expectedBizState.get(bizName) != null && expectedBizState.get(bizName).containsValue(BizState.ACTIVATED) && bizState == BizState.ACTIVATED) { throw new IllegalStateException(String.format( "Don't allow multi biz with same bizName to be active, config is %s. ", config)); } if (bizState == BizState.ACTIVATED) { if (progressBizState.get(bizName) != null && progressBizState.get(bizName).get(bizVersion) != null) { // switch operation if (progressBizState.get(bizName).get(bizVersion).equals(BizState.DEACTIVATED)) { BizOperation bizOperation = BizOperation.createBizOperation() .setBizName(bizName).setBizVersion(bizVersion) .setOperationType(BizOperation.OperationType.SWITCH) .setParameters(parameters); bizOperations.add(bizOperation); transformBizState(progressBizState, BizOperation.OperationType.SWITCH, bizName, bizVersion); } } else { // install operation BizOperation bizOperation = BizOperation.createBizOperation() .setBizName(bizName).setBizVersion(bizVersion) .setOperationType(BizOperation.OperationType.INSTALL) .setParameters(parameters); bizOperations.add(bizOperation); if (progressBizState.get(bizName) != null && progressBizState.get(bizName).containsValue(BizState.ACTIVATED)) { // add switch bizOperations.add(BizOperation.createBizOperation() .setOperationType(BizOperation.OperationType.SWITCH) .setBizName(bizName).setBizVersion(bizVersion)); transformBizState(progressBizState, BizOperation.OperationType.INSTALL, bizName, bizVersion); transformBizState(progressBizState, BizOperation.OperationType.SWITCH, bizName, bizVersion); } else { transformBizState(progressBizState, BizOperation.OperationType.INSTALL, bizName, bizVersion); } } } else { if (progressBizState.get(bizName) != null && progressBizState.get(bizName).get(bizVersion) == null && progressBizState.get(bizName).containsValue(BizState.ACTIVATED)) { BizOperation bizOperation = BizOperation.createBizOperation() .setBizName(bizName).setBizVersion(bizVersion) .setOperationType(BizOperation.OperationType.INSTALL) .setParameters(parameters); bizOperations.add(bizOperation); transformBizState(progressBizState, BizOperation.OperationType.INSTALL, bizName, bizVersion); } else if (progressBizState.get(bizName) == null || !BizState.DEACTIVATED.equals(progressBizState.get(bizName).get( bizVersion))) { throw new IllegalStateException(String.format( "Biz(%s:%s) cant be transform to %s, config is %s.", bizName, bizVersion, bizState, config)); } } if (expectedBizState.get(bizName) == null) { expectedBizState.put(bizName, new HashMap()); } expectedBizState.get(bizName).put(bizVersion, bizState); } for (String bizName : currentBizState.keySet()) { for (String bizVersion : currentBizState.get(bizName).keySet()) { if (expectedBizState.get(bizName) == null || !expectedBizState.get(bizName).containsKey(bizVersion)) { bizOperations.add(BizOperation.createBizOperation() .setOperationType(BizOperation.OperationType.UNINSTALL).setBizName(bizName) .setBizVersion(bizVersion)); transformBizState(progressBizState, BizOperation.OperationType.UNINSTALL, bizName, bizVersion); } } } // double check if (!checkBizState(expectedBizState, progressBizState)) { throw new IllegalStateException(String.format( "Failed to transform biz operation, config is %s.", config)); } return bizOperations; } /** * config format is similar to bizName:bizVersion:bizState;bizName:bizVersion:bizState * * @param config * @return */ public static boolean isValidConfig(String config) { for (String singleConfig : config.split(Constants.STRING_SEMICOLON)) { int idx = singleConfig.indexOf(Constants.QUESTION_MARK_SPLIT); String parameterStr = (idx == -1) ? "" : singleConfig.substring(idx + 1); String nvs = (idx == -1) ? singleConfig : singleConfig.substring(0, idx); String[] configInfo = nvs.split(Constants.STRING_COLON); if (configInfo.length != 3) { return false; } if (!BizState.ACTIVATED.name().equalsIgnoreCase(configInfo[2]) && !BizState.DEACTIVATED.name().equalsIgnoreCase(configInfo[2])) { return false; } if (!isValidParameter(parameterStr)) { return false; } } return true; } public static Map parseParameter(String config) { Map parameters = new HashMap<>(); if (!StringUtils.isEmpty(config)) { String[] keyValue = config.split(Constants.AMPERSAND_SPLIT); for (String kv : keyValue) { String[] paramSplit = kv.split(Constants.EQUAL_SPLIT); parameters.put(paramSplit[0], paramSplit[1]); } } return parameters; } public static boolean isValidParameter(String config) { if (!StringUtils.isEmpty(config)) { String[] keyValue = config.split(Constants.AMPERSAND_SPLIT); for (String kv : keyValue) { String[] paramSplit = kv.split(Constants.EQUAL_SPLIT); if (paramSplit.length != 2) { return false; } } } return true; } public static void transformBizState(Map> progressBizState, BizOperation.OperationType operationType, String bizName, String bizVersion) { if (BizOperation.OperationType.SWITCH.equals(operationType)) { if (progressBizState.get(bizName) != null) { for (String version : progressBizState.get(bizName).keySet()) { progressBizState.get(bizName).put(version, BizState.DEACTIVATED); } } progressBizState.get(bizName).put(bizVersion, BizState.ACTIVATED); } else if (BizOperation.OperationType.INSTALL.equals(operationType)) { if (progressBizState.get(bizName) != null && progressBizState.get(bizName).containsValue(BizState.ACTIVATED)) { progressBizState.get(bizName).put(bizVersion, BizState.DEACTIVATED); } else { if (progressBizState.get(bizName) == null) { progressBizState.put(bizName, new HashMap()); } progressBizState.get(bizName).put(bizVersion, BizState.ACTIVATED); } } else if (BizOperation.OperationType.UNINSTALL.equals(operationType)) { if (progressBizState.get(bizName) != null) { progressBizState.get(bizName).remove(bizVersion); } } } /** * prefer to handle activated state * * @param config */ public static ArrayList adjustOperationOrder(String config) { ArrayList activatedStateConfig = new ArrayList<>(); ArrayList deactivatedStateConfig = new ArrayList<>(); for (String configOperation : config.split(Constants.STRING_SEMICOLON)) { if (StringUtils.isEmpty(configOperation)) { continue; } int idx = configOperation.indexOf(Constants.QUESTION_MARK_SPLIT); String[] configInfo = (idx == -1) ? configOperation.split(Constants.STRING_COLON) : configOperation.substring(0, idx).split(Constants.STRING_COLON); BizState bizState = BizState.of(configInfo[2]); if (BizState.ACTIVATED.equals(bizState)) { activatedStateConfig.add(configOperation); } else { deactivatedStateConfig.add(configOperation); } } activatedStateConfig.addAll(deactivatedStateConfig); return activatedStateConfig; } public static boolean checkBizState(Map> expectedBizState, Map> progressBizState) { for (String bizName : expectedBizState.keySet()) { if (progressBizState.get(bizName) == null || progressBizState.get(bizName).keySet().size() != expectedBizState.get(bizName) .size()) { return false; } for (String bizVersion : expectedBizState.get(bizName).keySet()) { if (!expectedBizState.get(bizName).get(bizVersion) .equals(progressBizState.get(bizName).get(bizVersion))) { return false; } } } return true; } public static Map> cloneBizStateMap(Map> origin) { if (origin == null) { return null; } Map> duplicate = new HashMap<>(); for (String name : origin.keySet()) { if (duplicate.get(name) == null) { duplicate.put(name, new HashMap()); } for (String version : origin.get(name).keySet()) { duplicate.get(name).put(version, origin.get(name).get(version)); } } return duplicate; } } ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/main/java/com/alipay/sofa/ark/config/zk/ZookeeperConfigActivator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config.zk; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.common.log.ArkLogger; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.EnvironmentUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.config.ConfigProcessor; import com.alipay.sofa.ark.config.util.OperationTransformer; import com.alipay.sofa.ark.config.OperationProcessor; import com.alipay.sofa.ark.config.RegistryConfig; import com.alipay.sofa.ark.config.util.NetUtils; import com.alipay.sofa.ark.exception.ArkRuntimeException; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.event.AfterFinishDeployEvent; import com.alipay.sofa.ark.spi.event.AfterFinishStartupEvent; import com.alipay.sofa.ark.spi.model.PluginContext; import com.alipay.sofa.ark.spi.service.PluginActivator; import com.alipay.sofa.ark.spi.service.event.EventAdminService; import com.alipay.sofa.ark.spi.service.event.EventHandler; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.AuthInfo; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.api.ACLProvider; import org.apache.curator.framework.recipes.cache.*; import org.apache.curator.framework.state.ConnectionState; import org.apache.curator.framework.state.ConnectionStateListener; import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.data.ACL; import java.util.*; import static com.alipay.sofa.ark.spi.constant.Constants.CONFIG_SERVER_ENABLE; /** * @author qilong.zql * @author GengZhang * @since 0.6.0 */ public class ZookeeperConfigActivator implements PluginActivator { private final static ArkLogger LOGGER = ArkLoggerFactory .getLogger(ZookeeperConfigActivator.class); private boolean enableZkServer = EnvironmentUtils.getProperty( CONFIG_SERVER_ENABLE, "true") .equalsIgnoreCase("true"); /** * Zookeeper zkClient */ private CuratorFramework zkClient; /** * Root path of zk registry resource */ private String rootPath = Constants.ZOOKEEPER_CONTEXT_SPLIT; private String ipResourcePath = buildIpConfigPath(); private String bizResourcePath = buildMasterBizConfigPath(); private Deque ipConfigDeque = new ArrayDeque<>(5); private Deque bizConfigDeque = new ArrayDeque<>(5); private CuratorCache ipCuratorCache; private CuratorCache bizCuratorCache; @Override public void start(final PluginContext context) { if (!enableZkServer) { LOGGER.warn("config server is disabled."); return; } LOGGER.info("start zookeeper config activator"); String config = ArkConfigs.getStringValue(Constants.CONFIG_SERVER_ADDRESS); RegistryConfig registryConfig = ZookeeperConfigurator.buildConfig(config); String address = registryConfig.getAddress(); int idx = address.indexOf(Constants.ZOOKEEPER_CONTEXT_SPLIT); if (idx != -1) { rootPath = address.substring(idx); if (!rootPath.endsWith(Constants.ZOOKEEPER_CONTEXT_SPLIT)) { rootPath += Constants.ZOOKEEPER_CONTEXT_SPLIT; } address = address.substring(0, idx); } RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); CuratorFrameworkFactory.Builder zkClientBuilder = CuratorFrameworkFactory.builder() .connectString(address).sessionTimeoutMs(3 * registryConfig.getConnectTimeout()) .connectionTimeoutMs(registryConfig.getConnectTimeout()).canBeReadOnly(false) .retryPolicy(retryPolicy).defaultData(null); List authInfos = buildAuthInfo(registryConfig); if (!authInfos.isEmpty()) { zkClientBuilder = zkClientBuilder.aclProvider(getDefaultAclProvider()).authorization( authInfos); } zkClient = zkClientBuilder.build(); zkClient.getConnectionStateListenable().addListener(new ConnectionStateListener() { @Override public void stateChanged(CuratorFramework client, ConnectionState newState) { LOGGER.info("Reconnect to zookeeper, re-register config resource."); if (newState == ConnectionState.RECONNECTED) { unSubscribeIpConfig(); registryResource(ipResourcePath, CreateMode.EPHEMERAL); subscribeIpConfig(); } } }); zkClient.start(); registryResource(bizResourcePath, CreateMode.PERSISTENT); registryResource(ipResourcePath, CreateMode.EPHEMERAL); subscribeIpConfig(); subscribeBizConfig(); registerEventHandler(context); } @Override public void stop(PluginContext context) { if (!enableZkServer) { return; } if (Objects.nonNull(ipCuratorCache)) { try { ipCuratorCache.close(); } catch (Exception e) { // ignore } } if (Objects.nonNull(bizCuratorCache)) { try { bizCuratorCache.close(); } catch (Exception e) { // ignore } } zkClient.close(); } protected void registerEventHandler(final PluginContext context) { Optional currentData = this.bizCuratorCache.get(bizResourcePath); if (currentData.isPresent() && Objects.nonNull(currentData.get().getData())) { final String bizInitConfig = new String(currentData.get().getData()); EventAdminService eventAdminService = context.referenceService(EventAdminService.class) .getService(); eventAdminService.register(new EventHandler() { @Override public void handleEvent(AfterFinishDeployEvent event) { LOGGER.info("Start to process init app config: {}", bizInitConfig); OperationProcessor.process(OperationTransformer.transformToBizOperation( bizInitConfig, context)); } @Override public int getPriority() { return 0; } }); eventAdminService.register(new EventHandler() { @Override public void handleEvent(AfterFinishStartupEvent event) { ConfigProcessor.createConfigProcessor(context, ipConfigDeque, "ip-zookeeper-config").start(); ConfigProcessor.createConfigProcessor(context, bizConfigDeque, "app-zookeeper-config").start(); } @Override public int getPriority() { return 0; } }); } } protected void subscribeIpConfig() { this.ipCuratorCache = CuratorCache.builder(zkClient, ipResourcePath).build(); this.ipCuratorCache.listenable().addListener(new CuratorCacheListener() { private int version = -1; @Override public void event(Type type, ChildData oldChildData, ChildData currentChildData) { if (type == Type.NODE_CHANGED) { if (Objects.nonNull(currentChildData) && currentChildData.getStat().getVersion() > version) { version = currentChildData.getStat().getVersion(); String configData = new String(currentChildData.getData()); ipConfigDeque.add(configData); LOGGER.info("Receive ip config data: {}, version is {}.", configData, version); } } } }); try { LOGGER.info("Subscribe ip config: {}.", ipResourcePath); ipCuratorCache.start(); } catch (Exception e) { throw new ArkRuntimeException("Failed to subscribe ip resource path.", e); } } protected void unSubscribeIpConfig() { if (Objects.nonNull(ipCuratorCache)) { try { LOGGER.info("Un-subscribe ip config: {}.", ipResourcePath); ipCuratorCache.close(); } catch (Throwable throwable) { LOGGER.error("Failed to un-subscribe ip resource path."); } ipCuratorCache = null; } } protected void subscribeBizConfig() { this.bizCuratorCache = CuratorCache.build(zkClient, bizResourcePath); this.bizCuratorCache.listenable().addListener(new CuratorCacheListener() { private int version = -1; @Override public void event(Type type, ChildData oldChildData, ChildData currentChildData) { if (type == Type.NODE_CHANGED) { if (Objects.nonNull(currentChildData) && currentChildData.getStat().getVersion() > version) { version = currentChildData.getStat().getVersion(); String configData = new String(currentChildData.getData()); bizConfigDeque.add(configData); LOGGER.info("Receive app config data: {}, version is {}.", configData, version); } } } }); try { bizCuratorCache.start(); } catch (Exception e) { throw new ArkRuntimeException("Failed to subscribe resource path.", e); } } protected void registryResource(String path, CreateMode createMode) { try { LOGGER.info("Registry context path: {} with mode: {}.", path, createMode); zkClient.create().creatingParentContainersIfNeeded().withMode(createMode).forPath(path); } catch (KeeperException.NodeExistsException nodeExistsException) { if (LOGGER.isWarnEnabled()) { LOGGER.warn("Context path has exists in zookeeper, path=" + path); } } catch (Exception e) { throw new ArkRuntimeException("Failed to register resource to zookeeper registry!", e); } } public String buildIpConfigPath() { return buildMasterBizRootPath().append(Constants.ZOOKEEPER_CONTEXT_SPLIT) .append(NetUtils.getLocalHostAddress()).toString(); } public String buildMasterBizConfigPath() { return buildMasterBizRootPath().toString(); } private StringBuilder buildMasterBizRootPath() { StringBuilder masterBizRootPath = new StringBuilder(rootPath); String masterBizName = ArkConfigs.getStringValue(Constants.MASTER_BIZ); AssertUtils.isFalse(StringUtils.isEmpty(masterBizName), "Master biz should be specified."); String configEnvironment = ArkConfigs.getStringValue(Constants.CONFIG_SERVER_ENVIRONMENT, "sofa-ark"); masterBizRootPath.append(configEnvironment).append(Constants.ZOOKEEPER_CONTEXT_SPLIT) .append(masterBizName); return masterBizRootPath; } /** * build auth info * * @return */ private List buildAuthInfo(RegistryConfig registryConfig) { List info = new ArrayList(); String scheme = registryConfig.getParameter("scheme"); //addAuth=user1:password1,user2:password2 String addAuth = registryConfig.getParameter("addAuth"); if (!StringUtils.isEmpty(addAuth)) { String[] authList = addAuth.split(","); for (String singleAuthInfo : authList) { info.add(new AuthInfo(scheme, singleAuthInfo.getBytes())); } } return info; } /** * Get default AclProvider * * @return */ private ACLProvider getDefaultAclProvider() { return new ACLProvider() { @Override public List getDefaultAcl() { return ZooDefs.Ids.CREATOR_ALL_ACL; } @Override public List getAclForPath(String path) { return ZooDefs.Ids.CREATOR_ALL_ACL; } }; } } ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/main/java/com/alipay/sofa/ark/config/zk/ZookeeperConfigurator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config.zk; import com.alipay.sofa.ark.common.util.AssertUtils; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.config.RegistryConfig; import com.alipay.sofa.ark.spi.constant.Constants; import java.util.HashMap; import java.util.Map; /** * @author qilong.zql * @author LiWei * @since 0.6.0 */ public class ZookeeperConfigurator { public static RegistryConfig buildConfig(String config) { String zkAddress = parseAddress(config); Map parameters = parseParam(config); return new RegistryConfig().setAddress(zkAddress) .setProtocol(Constants.CONFIG_PROTOCOL_ZOOKEEPER).setParameters(parameters); } /** * parse address * * @param config config value */ public static String parseAddress(String config) { AssertUtils.isFalse(StringUtils.isEmpty(config), "Zookeeper config should not be empty."); AssertUtils.isTrue(config.startsWith(Constants.CONFIG_PROTOCOL_ZOOKEEPER_HEADER), "Zookeeper config should start with 'zookeeper://'."); String value = config.substring(Constants.CONFIG_PROTOCOL_ZOOKEEPER_HEADER.length()); int idx = value.indexOf(Constants.QUESTION_MARK_SPLIT); String address = (idx == -1) ? value : value.substring(0, idx); AssertUtils.isFalse(StringUtils.isEmpty(address), "Zookeeper address should not be empty"); return address; } /** * parse params * * @param config * @return */ public static Map parseParam(String config) { AssertUtils.assertNotNull(config, "Params should not be null."); Map map = new HashMap<>(); int idx = config.indexOf(Constants.QUESTION_MARK_SPLIT); String paramString = (idx == -1) ? Constants.EMPTY_STR : config.substring(idx + 1); String[] paramSplit = paramString.split(Constants.AMPERSAND_SPLIT); for (String param : paramSplit) { if (!StringUtils.isEmpty(param)) { String[] kvSplit = param.split(Constants.EQUAL_SPLIT); AssertUtils.isTrue(kvSplit.length == 2, String.format("Config parameter %s is invalid format.", kvSplit)); map.put(kvSplit[0], kvSplit[1]); } } return map; } } ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/test/java/com/alipay/sofa/ark/config/ApolloConfigActivatorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config; import com.alipay.sofa.ark.config.apollo.ApolloConfigActivator; import com.alipay.sofa.ark.spi.model.PluginContext; import com.ctrip.framework.apollo.ConfigChangeListener; import com.ctrip.framework.apollo.ConfigService; import com.ctrip.framework.apollo.enums.PropertyChangeType; import com.ctrip.framework.apollo.model.ConfigChange; import com.ctrip.framework.apollo.model.ConfigChangeEvent; import com.google.common.collect.ImmutableMap; import org.junit.Assert; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import java.lang.reflect.Field; import java.util.List; import java.util.Map; import static com.alipay.sofa.ark.spi.constant.Constants.APOLLO_MASTER_BIZ_KEY; import static com.alipay.sofa.ark.spi.constant.Constants.CONFIG_APOLLO_NAMESPACE; import static org.mockito.ArgumentMatchers.any; /** * @author zsk * @version $Id: ApolloConfigActivatorTest.java, v 0.1 2023年10月11日 18:22 zsk Exp $ */ public class ApolloConfigActivatorTest { @Test public void testStartStop() throws NoSuchFieldException, IllegalAccessException { MockApolloConfig mockConfig = new MockApolloConfig(); MockedStatic mockService = Mockito.mockStatic(ConfigService.class); mockService.when(() -> ConfigService.getConfig(CONFIG_APOLLO_NAMESPACE)) .thenReturn(mockConfig); ApolloConfigActivator apolloActivator = new ApolloConfigActivator(); PluginContext pluginContext = Mockito.mock(PluginContext.class); Mockito.doThrow(new RuntimeException("mock referenceService Failed")) .when(pluginContext).referenceService(any()); //test start apolloActivator.start(pluginContext); Field field = ApolloConfigActivator.class.getDeclaredField("changeListener"); field.setAccessible(true); Object changeListener = field.get(apolloActivator); Assert.assertNotNull(changeListener); List list = mockConfig.getListeners(); Assert.assertTrue(1 == list.size()); String newValue = "nameA:vA:activated;nameA:vB:deactivated"; ConfigChange configChange = new ConfigChange(CONFIG_APOLLO_NAMESPACE, APOLLO_MASTER_BIZ_KEY, "", newValue, PropertyChangeType.MODIFIED); Map changeMap = ImmutableMap.of(APOLLO_MASTER_BIZ_KEY, configChange); ConfigChangeEvent changeEvent = new ConfigChangeEvent(CONFIG_APOLLO_NAMESPACE, changeMap); //test apollo onChange event: match masterBiz key but OperationProcessor.process failed Throwable throwable = null; try { list.get(0).onChange(changeEvent); } catch (Throwable t) { throwable = t; } Assert.assertNotNull(throwable); Assert.assertTrue(throwable.getMessage().equals( "mock referenceService Failed")); //stop apolloActivator.stop(null); list = mockConfig.getListeners(); Assert.assertTrue(0 == list.size()); } } ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/test/java/com/alipay/sofa/ark/config/ConfigBaseActivatorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.common.util.EnvironmentUtils; import com.alipay.sofa.ark.config.apollo.ApolloConfigActivator; import com.alipay.sofa.ark.spi.service.PluginActivator; import org.junit.Assert; import org.junit.Test; import static com.alipay.sofa.ark.spi.constant.Constants.CONFIG_SERVER_ENABLE; import static com.alipay.sofa.ark.spi.constant.Constants.CONFIG_SERVER_TYPE; /** * @author zsk * @version $Id: ConfigBaseActivatorTest.java, v 0.1 2023年10月11日 17:42 zsk Exp $ */ public class ConfigBaseActivatorTest { @Test public void testGetConfigActivator() { EnvironmentUtils.setProperty(CONFIG_SERVER_ENABLE, "true"); ArkConfigs.putStringValue(CONFIG_SERVER_TYPE, ConfigTypeEnum.apollo.name()); ConfigBaseActivator baseActivator = new ConfigBaseActivator(); PluginActivator activator = baseActivator.getConfigActivator(); Assert.assertTrue(activator instanceof ApolloConfigActivator); } @Test public void testFailNewZookeeperConfigurator() { EnvironmentUtils.setProperty(CONFIG_SERVER_ENABLE, "true"); ArkConfigs.putStringValue(CONFIG_SERVER_TYPE, ConfigTypeEnum.zookeeper.name()); ConfigBaseActivator baseActivator = new ConfigBaseActivator(); Throwable throwable = null; try { baseActivator.start(null); } catch (Throwable t) { throwable = t; } Assert.assertNotNull(throwable); Assert.assertTrue(throwable.getMessage().equals("Master biz should be specified.")); } } ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/test/java/com/alipay/sofa/ark/config/MockApolloConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config; import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.ConfigChangeListener; import com.ctrip.framework.apollo.enums.ConfigSourceType; import com.google.common.base.Function; import com.google.common.collect.Lists; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Set; /** * @author zsk * @version $Id: MockApolloConfig.java, v 0.1 2023年10月11日 20:26 zsk Exp $ */ public class MockApolloConfig implements Config { private final List m_listeners = Lists.newCopyOnWriteArrayList(); @Override public String getProperty(String key, String defaultValue) { return null; } @Override public Integer getIntProperty(String key, Integer defaultValue) { return null; } @Override public Long getLongProperty(String key, Long defaultValue) { return null; } @Override public Short getShortProperty(String key, Short defaultValue) { return null; } @Override public Float getFloatProperty(String key, Float defaultValue) { return null; } @Override public Double getDoubleProperty(String key, Double defaultValue) { return null; } @Override public Byte getByteProperty(String key, Byte defaultValue) { return null; } @Override public Boolean getBooleanProperty(String key, Boolean defaultValue) { return null; } @Override public String[] getArrayProperty(String key, String delimiter, String[] defaultValue) { return new String[0]; } @Override public Date getDateProperty(String key, Date defaultValue) { return null; } @Override public Date getDateProperty(String key, String format, Date defaultValue) { return null; } @Override public Date getDateProperty(String key, String format, Locale locale, Date defaultValue) { return null; } @Override public > T getEnumProperty(String key, Class enumType, T defaultValue) { return null; } @Override public long getDurationProperty(String key, long defaultValue) { return 0; } public List getListeners() { return m_listeners; } @Override public void addChangeListener(ConfigChangeListener listener) { addChangeListener(listener, null); } @Override public void addChangeListener(ConfigChangeListener listener, Set interestedKeys) { addChangeListener(listener, interestedKeys, null); } @Override public void addChangeListener(ConfigChangeListener listener, Set interestedKeys, Set interestedKeyPrefixes) { if (!m_listeners.contains(listener)) { m_listeners.add(listener); } } @Override public boolean removeChangeListener(ConfigChangeListener listener) { return m_listeners.remove(listener); } @Override public Set getPropertyNames() { return null; } @Override public T getProperty(String key, Function function, T defaultValue) { return null; } @Override public ConfigSourceType getSourceType() { return null; } } ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/test/java/com/alipay/sofa/ark/config/OperationTransformerTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config; import com.alipay.sofa.ark.api.ArkClient; import com.alipay.sofa.ark.api.ArkConfigs; import com.alipay.sofa.ark.common.util.StringUtils; import com.alipay.sofa.ark.config.util.OperationTransformer; import com.alipay.sofa.ark.spi.constant.Constants; import com.alipay.sofa.ark.spi.model.Biz; import com.alipay.sofa.ark.spi.model.BizOperation; import com.alipay.sofa.ark.spi.model.BizState; import com.alipay.sofa.ark.spi.model.PluginContext; import com.alipay.sofa.ark.spi.registry.ServiceReference; import com.alipay.sofa.ark.spi.service.biz.BizManagerService; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; /** * @author qilong.zql * @since 0.6.0 */ public class OperationTransformerTest { @Test public void testBizFilePath() { File file = ArkClient.createBizSaveFile("name", "version"); Assert.assertTrue(StringUtils.isEmpty(ArkConfigs .getStringValue(Constants.CONFIG_INSTALL_BIZ_DIR))); Assert.assertTrue(file.getAbsolutePath().contains( "sofa-ark" + File.separator + "name-version-")); } @Test public void testTransformFormatError() { Assert.assertFalse(OperationTransformer.isValidConfig("aaa")); Assert.assertFalse(OperationTransformer.isValidConfig("name:version:resolved")); Assert.assertFalse(OperationTransformer.isValidConfig("name:version:activated?url")); Assert.assertTrue(OperationTransformer .isValidConfig("name:version:activated?url=http://xx")); Assert.assertTrue(OperationTransformer .isValidConfig("name:version:activated?url=http://xx¶m2=value2")); String config = "k1=v1&k2=v2"; Map params = OperationTransformer.parseParameter(config); Assert.assertEquals(2, params.size()); Assert.assertEquals("v1", params.get("k1")); Assert.assertEquals("v2", params.get("k2")); } @Test public void testTransformConfigOperationWithConflictState() { Map> currentBizState = new HashMap<>(); Exception ex = null; List operations = null; try { operations = OperationTransformer.doTransformToBizOperation( "n1:v1:activated;n1:v1:deactivated", currentBizState); } catch (IllegalStateException e) { ex = e; } Assert.assertNotNull(ex); Assert.assertTrue(ex.getMessage() .contains("Don't specify same biz with different bizState")); } @Test public void testTransformUninstallConfigOperation() { List bizOperations = OperationTransformer.doTransformToBizOperation("", mockBizState()); Assert.assertEquals(3, bizOperations.size()); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameA").setBizVersion("vA") .setOperationType(BizOperation.OperationType.UNINSTALL))); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameA").setBizVersion("vB") .setOperationType(BizOperation.OperationType.UNINSTALL))); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameB").setBizVersion("vA") .setOperationType(BizOperation.OperationType.UNINSTALL))); } @Test public void testTransformConfigOperationWithMultiActivateState() { Map> currentBizState = new HashMap<>(); Exception ex = null; List operations = null; try { operations = OperationTransformer.doTransformToBizOperation( "n1:v1:activated;n1:v2:activated", currentBizState); } catch (IllegalStateException e) { ex = e; } Assert.assertNotNull(ex); Assert.assertTrue(ex.getMessage().contains( "Don't allow multi biz with same bizName to be active")); } @Test public void testTransformConfigOperationNotAllowed() { Map> currentBizState = new HashMap<>(); Exception ex = null; List operations = null; try { operations = OperationTransformer.doTransformToBizOperation( "n1:v1:deactivated;n1:v2:deactivated", currentBizState); } catch (IllegalStateException e) { ex = e; } Assert.assertNotNull(ex); Assert.assertTrue(ex.getMessage().contains("cant be transform to")); } @Test public void testTransformUnInstallOperation() { List bizOperations = OperationTransformer.doTransformToBizOperation( "nameA:vA:activated", mockBizState()); Assert.assertEquals(2, bizOperations.size()); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameA").setBizVersion("vB") .setOperationType(BizOperation.OperationType.UNINSTALL))); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameB").setBizVersion("vA") .setOperationType(BizOperation.OperationType.UNINSTALL))); } @Test public void testTransformInstallOperation() { List bizOperations = OperationTransformer.doTransformToBizOperation( "nameA:vA:activated;nameA:vB:deactivated;nameB:vA:activated;nameB:vB:deactivated", mockBizState()); Assert.assertEquals(1, bizOperations.size()); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameB").setBizVersion("vB") .setOperationType(BizOperation.OperationType.INSTALL))); } @Test public void testTransformInstallAndUninstallOperation() { List bizOperations = OperationTransformer.doTransformToBizOperation( "nameA:vA:activated;nameB:vA:activated;nameB:vB:deactivated", mockBizState()); Assert.assertEquals(2, bizOperations.size()); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameA").setBizVersion("vB") .setOperationType(BizOperation.OperationType.UNINSTALL))); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameB").setBizVersion("vB") .setOperationType(BizOperation.OperationType.INSTALL))); } @Test public void testTransformSwitchOperation() { List bizOperations = OperationTransformer.doTransformToBizOperation( "nameA:vA:deactivated;nameA:vB:activated;nameB:vA:activated", mockBizState()); Assert.assertEquals(1, bizOperations.size()); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameA").setBizVersion("vB") .setOperationType(BizOperation.OperationType.SWITCH))); } @Test public void testTransformInstallAndSwitch() { List bizOperations = OperationTransformer.doTransformToBizOperation( "nameA:vA:activated;nameA:vB:deactivated;nameB:vA:deactivated;nameB:vB:activated", mockBizState()); Assert.assertEquals(2, bizOperations.size()); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameB").setBizVersion("vB") .setOperationType(BizOperation.OperationType.INSTALL))); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameB").setBizVersion("vB") .setOperationType(BizOperation.OperationType.SWITCH))); } @Test public void testTransformUninstallAndSwitch() { List bizOperations = OperationTransformer.doTransformToBizOperation( "nameA:vB:activated;nameB:vA:activated", mockBizState()); Assert.assertEquals(2, bizOperations.size()); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameA").setBizVersion("vA") .setOperationType(BizOperation.OperationType.UNINSTALL))); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameA").setBizVersion("vB") .setOperationType(BizOperation.OperationType.SWITCH))); } @Test public void testTransformUninstallAndInstall() { List bizOperations = OperationTransformer.doTransformToBizOperation( "nameA:vA:activated;nameA:vB:deactivated;nameB:vB:activated", mockBizState()); Assert.assertEquals(3, bizOperations.size()); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameB").setBizVersion("vB") .setOperationType(BizOperation.OperationType.INSTALL))); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameB").setBizVersion("vB") .setOperationType(BizOperation.OperationType.SWITCH))); Assert.assertTrue(bizOperations.contains(BizOperation.createBizOperation() .setBizName("nameB").setBizVersion("vA") .setOperationType(BizOperation.OperationType.UNINSTALL))); } @Test public void testTransformUnstableState() { Exception ex = null; try { List bizList = new ArrayList<>(); Biz biz1 = Mockito.mock(Biz.class); Biz biz2 = Mockito.mock(Biz.class); Biz biz3 = Mockito.mock(Biz.class); bizList.add(biz1); bizList.add(biz2); bizList.add(biz3); when(biz1.getBizName()).thenReturn("nameA"); when(biz1.getBizVersion()).thenReturn("vA"); when(biz1.getBizState()).thenReturn(BizState.ACTIVATED); when(biz1.getBizName()).thenReturn("nameA"); when(biz1.getBizVersion()).thenReturn("vB"); when(biz1.getBizState()).thenReturn(BizState.RESOLVED); when(biz1.getBizName()).thenReturn("nameB"); when(biz1.getBizVersion()).thenReturn("vA"); when(biz1.getBizState()).thenReturn(BizState.ACTIVATED); PluginContext pluginContext = Mockito.mock(PluginContext.class); ServiceReference serviceReference = Mockito.mock(ServiceReference.class); BizManagerService bizManagerService = Mockito.mock(BizManagerService.class); when(serviceReference.getService()).thenReturn(bizManagerService); when(pluginContext.referenceService(any(Class.class))).thenReturn(serviceReference); when(bizManagerService.getBizInOrder()).thenReturn(bizList); OperationTransformer.transformToBizOperation( "nameA:vA:activated;nameA:vB:deactivated;nameB:vB:activated", pluginContext); } catch (Exception e) { ex = e; } Assert.assertNotNull(ex); Assert.assertTrue(ex.getMessage().contains("Exist illegal biz")); } @Test public void testTransformUnChangedState() { List bizOperations = OperationTransformer.doTransformToBizOperation( "nameA:vA:activated;nameA:vB:deactivated;nameB:vA:activated", mockBizState()); Assert.assertEquals(0, bizOperations.size()); } private Map> mockBizState() { Map> bizStateMap = new LinkedHashMap>(); Map bizAVersionStateMap = new HashMap(); bizAVersionStateMap.put("vA", BizState.ACTIVATED); bizAVersionStateMap.put("vB", BizState.DEACTIVATED); bizStateMap.put("nameA", bizAVersionStateMap); Map bizBVersionStateMap = new HashMap(); bizBVersionStateMap.put("vA", BizState.ACTIVATED); bizStateMap.put("nameB", bizBVersionStateMap); return bizStateMap; } } ================================================ FILE: sofa-ark-plugin/config-ark-plugin/src/test/java/com/alipay/sofa/ark/config/ZookeeperConfiguratorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.config; import com.alipay.sofa.ark.config.zk.ZookeeperConfigurator; import com.alipay.sofa.ark.spi.constant.Constants; import org.junit.Assert; import org.junit.Test; import java.util.Map; /** * @author qilong.zql * @since 0.6.0 */ public class ZookeeperConfiguratorTest { @Test public void testInvalidZookeeperAddress() { Throwable throwable = null; try { ZookeeperConfigurator.buildConfig("xxx"); } catch (Throwable t) { throwable = t; } Assert.assertNotNull(throwable); Assert.assertTrue(throwable.getMessage().equals( "Zookeeper config should start with 'zookeeper://'.")); } @Test public void testRegistryConfig() { RegistryConfig registryConfig = ZookeeperConfigurator .buildConfig("zookeeper://localhost:2181?k1=v1&k2=v2"); Assert.assertEquals(Constants.CONFIG_PROTOCOL_ZOOKEEPER, registryConfig.getProtocol()); Assert.assertEquals("localhost:2181", registryConfig.getAddress()); Assert.assertEquals(2, registryConfig.getParameters().size()); Assert.assertEquals("v1", registryConfig.getParameter("k1")); Assert.assertEquals("v2", registryConfig.getParameter("k2")); } @Test public void testZookeeperAddress() { String address = ZookeeperConfigurator .parseAddress("zookeeper://localhost:2181?k1=v1&k2=v2"); Assert.assertEquals("localhost:2181", address); } @Test public void testZookeeperParameter() { Map parameters = ZookeeperConfigurator .parseParam("zookeeper://localhost:2181?k1=v1&k2=v2"); Assert.assertEquals(2, parameters.size()); Assert.assertEquals("v1", parameters.get("k1")); Assert.assertEquals("v2", parameters.get("k2")); } @Test public void testInvalidZookeeperParameter() { Throwable throwable = null; try { ZookeeperConfigurator.parseParam("zookeeper://localhost:2181?k1"); } catch (Throwable t) { throwable = t; } Assert.assertNotNull(throwable); Assert.assertTrue(throwable.getMessage().contains("invalid format")); } } ================================================ FILE: sofa-ark-plugin/netty-ark-plugin/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `netty-ark-plugin` **Package**: `com.alipay.sofa.ark.netty` This is a built-in Ark Plugin that provides Netty-based telnet server support for runtime management. ## Purpose - Enable remote telnet access to Ark container - Provide command-line interface for runtime operations - Support management commands: biz, plugin, info queries ## Key Features ### Telnet Server - Netty-based telnet server implementation - Listen on configurable port - Command parsing and execution ### Supported Commands - `biz` - Manage business modules (install, uninstall, switch, check) - `plugin` - Manage plugins (check, install) - `info` - Query container and module information - `help` - Show available commands ## Usage Add plugin dependency: ```xml com.alipay.sofa netty-ark-plugin ark-plugin ``` Connect via telnet: ```bash telnet localhost 1234 ``` ## Dependencies - `sofa-ark-spi` - Service interfaces - `sofa-ark-api` - API for operations - `netty-all` - Netty networking framework ## Used By - Applications requiring remote management capabilities - Operations teams for runtime monitoring ================================================ FILE: sofa-ark-plugin/netty-ark-plugin/pom.xml ================================================ 4.0.0 sofa-ark-bom com.alipay.sofa ${sofa.ark.version} ../../sofa-ark-bom netty-ark-plugin ${project.groupId}:${project.artifactId} org.springframework.boot spring-boot-starter-webflux ${spring.boot.version} provided io.projectreactor.netty reactor-netty ${reactor-netty.version} provided com.alipay.sofa sofa-ark-common ${project.version} compile com.alipay.sofa sofa-ark-plugin-maven-plugin ${project.version} default-cli ark-plugin com.alipay.sofa.ark.NettyPluginActivator *:*:* com.alipay.sofa:netty-ark-plugin ================================================ FILE: sofa-ark-plugin/netty-ark-plugin/src/main/java/com/alipay/sofa/ark/NettyPluginActivator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.netty.EmbeddedServerServiceImpl; import com.alipay.sofa.ark.spi.model.PluginContext; import com.alipay.sofa.ark.spi.service.PluginActivator; import com.alipay.sofa.ark.spi.web.EmbeddedServerService; import reactor.netty.http.server.HttpServer; public class NettyPluginActivator implements PluginActivator { private EmbeddedServerService embeddedNettyService = new EmbeddedServerServiceImpl(); @Override public void start(PluginContext context) { context.publishService(EmbeddedServerService.class, embeddedNettyService); } @Override public void stop(PluginContext context) { for (Object o : embeddedNettyService) { if (!(o instanceof HttpServer)) { continue; } HttpServer webServer = (HttpServer) o; try { //webServer.stop(); } catch (Exception ex) { ArkLoggerFactory.getDefaultLogger().error("Unable to stop embedded Netty", ex); } } } } ================================================ FILE: sofa-ark-plugin/netty-ark-plugin/src/main/java/com/alipay/sofa/ark/netty/ArkNettyIdentification.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.netty; public class ArkNettyIdentification { } ================================================ FILE: sofa-ark-plugin/netty-ark-plugin/src/main/java/com/alipay/sofa/ark/netty/EmbeddedServerServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.netty; import com.alipay.sofa.ark.spi.web.AbstractEmbeddedServerService; import reactor.netty.http.server.HttpServer; public class EmbeddedServerServiceImpl extends AbstractEmbeddedServerService { } ================================================ FILE: sofa-ark-plugin/pom.xml ================================================ sofa-ark-bom com.alipay.sofa ${sofa.ark.version} ../sofa-ark-bom 4.0.0 sofa-ark-plugin pom ${project.groupId}:${project.artifactId} config-ark-plugin netty-ark-plugin web-ark-plugin ================================================ FILE: sofa-ark-plugin/web-ark-plugin/CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Module Overview **Artifact ID**: `web-ark-plugin` **Package**: `com.alipay.sofa.ark.web` This is a built-in Ark Plugin that provides embedded web server support for business modules. ## Purpose - Enable multiple web applications in different Biz modules - Provide embedded Tomcat server support - Handle web context isolation between Biz modules ## Key Features ### Embedded Server - Starts embedded Tomcat server - Each Biz can have its own web context path - Supports Spring Boot web applications ### Web Context Isolation - Different Biz modules can serve at different context paths - Static resource isolation - Session isolation support ## Usage Add plugin dependency: ```xml com.alipay.sofa web-ark-plugin ark-plugin ``` Configure web context in Maven plugin: ```xml /my-app ``` ## Dependencies - `sofa-ark-spi` - Service interfaces - `sofa-ark-api` - API for operations - Tomcat embedded (provided scope) - Spring Boot (provided scope) ## Used By - Web applications running on SOFAArk - Multiple web applications merged into single deployment ================================================ FILE: sofa-ark-plugin/web-ark-plugin/pom.xml ================================================ sofa-ark-bom com.alipay.sofa ${sofa.ark.version} ../../sofa-ark-bom 4.0.0 web-ark-plugin ${project.groupId}:${project.artifactId} org.springframework.boot spring-boot ${spring.boot.version} provided org.springframework.boot spring-boot-autoconfigure ${spring.boot.version} provided org.springframework.boot spring-boot-starter-tomcat ${spring.boot.version} provided com.alipay.sofa sofa-ark-common org.mockito mockito-inline test junit junit test org.springframework.boot spring-boot-starter-test ${spring.boot.version} test com.alipay.sofa sofa-ark-plugin-maven-plugin ${project.version} default-cli ark-plugin com.alipay.sofa.ark.web.embed.WebPluginActivator com.alipay.sofa.ark.web.embed.tomcat.SwitchClassLoaderAutoConfiguration *:*:* com.alipay.sofa:web-ark-plugin org.apache.maven.plugins maven-surefire-plugin org.apache.maven.surefire surefire-junit47 ${surefire.version} ================================================ FILE: sofa-ark-plugin/web-ark-plugin/src/main/java/com/alipay/sofa/ark/web/embed/WebPluginActivator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.web.embed; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import com.alipay.sofa.ark.spi.model.PluginContext; import com.alipay.sofa.ark.spi.service.PluginActivator; import com.alipay.sofa.ark.spi.web.EmbeddedServerService; import com.alipay.sofa.ark.web.embed.tomcat.EmbeddedServerServiceImpl; import org.apache.catalina.startup.Tomcat; /** * @author qilong.zql * @since 0.6.0 */ public class WebPluginActivator implements PluginActivator { EmbeddedServerService embeddedServerService = new EmbeddedServerServiceImpl(); @Override public void start(PluginContext context) { context.publishService(EmbeddedServerService.class, embeddedServerService); } @Override public void stop(PluginContext context) { for (Object o : embeddedServerService) { if (!(o instanceof Tomcat)) { continue; } Tomcat webServer = (Tomcat) o; try { webServer.destroy(); } catch (Exception ex) { ArkLoggerFactory.getDefaultLogger().warn("Unable to stop embedded Tomcat", ex); } } } } ================================================ FILE: sofa-ark-plugin/web-ark-plugin/src/main/java/com/alipay/sofa/ark/web/embed/tomcat/ArkTomcatEmbeddedWebappClassLoader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.web.embed.tomcat; import com.alipay.sofa.ark.common.log.ArkLoggerFactory; import org.apache.catalina.loader.ParallelWebappClassLoader; import org.slf4j.Logger; import java.io.IOException; import java.net.URL; import java.util.Collections; import java.util.Enumeration; /** * Extension of Tomcat's {@link ParallelWebappClassLoader} that does not consider the * {@link ClassLoader#getSystemClassLoader() system classloader}. This is required to * ensure that any custom context class loader is always used (as is the case with some * executable archives). * * There are two case to initialize tomcat for multi biz model: * * 1. When included this Tomcat ClassLoader by * {@link com.alipay.sofa.ark.web.embed.tomcat.ArkTomcatEmbeddedWebappClassLoader}, * this will ensure there should always be one tomcat instance with only one port. * In this case, each module use same web port, must define different web context path. * * -------------- * │ Biz Module A │\ * -------------- \ * \----→ Tomcat 1: Port 1 * -------------- / * │ Biz Module B │/ * -------------- * * 2. If not included, then each biz module with mvc will create a tomcat, * In this case, each module can define any web context path, but must define different web server port. * * -------------- * │ Biz Module A │ ----→ Tomcat 1: Port 1 * -------------- * * -------------- * │ Biz Module B │ ----→ Tomcat 2: Port2 * -------------- * * Actually, the mostly used in prod env is the first case, so we need to include a web plugin. * * @author qilong.zql * @author Phillip Webb * @since 0.6.0 */ public class ArkTomcatEmbeddedWebappClassLoader extends ParallelWebappClassLoader { private static final Logger LOGGER = ArkLoggerFactory .getLogger(ArkTomcatEmbeddedWebappClassLoader.class); static { ClassLoader.registerAsParallelCapable(); } public ArkTomcatEmbeddedWebappClassLoader() { } /** * NOTE: super web class loader will set parent to systemClassLoader if 'parent' param value is null. * So 'parent' class loader usually would not be null. * * @param parent */ public ArkTomcatEmbeddedWebappClassLoader(ClassLoader parent) { super(parent); } @Override public URL findResource(String name) { return null; } @Override public Enumeration findResources(String name) throws IOException { return Collections.emptyEnumeration(); } @Override public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class result = findExistingLoadedClass(name); result = (result != null) ? result : doLoadClass(name); if (result == null) { throw new ClassNotFoundException(name); } return resolveIfNecessary(result, resolve); } } /** * We try to load class from cache in current class loader chain. * * @param name * @return */ private Class findExistingLoadedClass(String name) { Class resultClass = findLoadedClass0(name); resultClass = (resultClass != null) ? resultClass : findLoadedClass(name); return resultClass; } /** * doLoadClass is used to handle class not found from cache, so doLoadClass try to load class from class loader chain and put it into class cache. * * @param name * @return * @throws ClassNotFoundException */ private Class doLoadClass(String name) throws ClassNotFoundException { checkPackageAccess(name); // Note: set delegate TRUE will load class from parent class loader first, otherwise from current class loader first. // If class is javax or apache class, filter will return true, otherwise filter return false. if ((this.delegate || filter(name, true))) { Class result = loadFromParent(name); return (result != null) ? result : findClassIgnoringNotFound(name); } Class result = findClassIgnoringNotFound(name); return (result != null) ? result : loadFromParent(name); } private Class resolveIfNecessary(Class resultClass, boolean resolve) { if (resolve) { resolveClass(resultClass); } return (resultClass); } @Override protected void addURL(URL url) { // Ignore URLs added by the Tomcat 8 implementation (see gh-919) if (LOGGER.isTraceEnabled()) { LOGGER.trace("Ignoring request to add " + url + " to the tomcat classloader"); } } private Class loadFromParent(String name) { if (this.parent == null) { return null; } try { return Class.forName(name, false, this.parent); } catch (ClassNotFoundException ex) { return null; } } /** * Invoke findClass in tomcat web class loader. * Note: After Tomcat stopped, Tomcat will construct a IllegalStateException, and then put it into a ClassNotFoundException. * SOFAArk will always treat this inner IllegalStateException as a ClassNotFoundException and return null, * which means this Class was not found and then find this class through parent class loader (e.g. ApplicationClassLoader). * * @param name * @return */ private Class findClassIgnoringNotFound(String name) { try { return findClass(name); } catch (ClassNotFoundException ex) { return null; } } void checkPackageAccess(String name) throws ClassNotFoundException { if (this.securityManager != null && name.lastIndexOf('.') >= 0) { try { this.securityManager.checkPackageAccess(name.substring(0, name.lastIndexOf('.'))); } catch (SecurityException ex) { throw new ClassNotFoundException("Security Violation, attempt to use " + "Restricted Class: " + name, ex); } } } } ================================================ FILE: sofa-ark-plugin/web-ark-plugin/src/main/java/com/alipay/sofa/ark/web/embed/tomcat/EmbeddedServerServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.web.embed.tomcat; import com.alipay.sofa.ark.spi.web.AbstractEmbeddedServerService; import org.apache.catalina.startup.Tomcat; /** * This implementation would be published as ark service. * * @author qilong.zql * @since 0.6.0 */ public class EmbeddedServerServiceImpl extends AbstractEmbeddedServerService { } ================================================ FILE: sofa-ark-plugin/web-ark-plugin/src/main/java/com/alipay/sofa/ark/web/embed/tomcat/SwitchClassLoaderAutoConfiguration.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.web.embed.tomcat; import org.apache.catalina.startup.Tomcat; import org.apache.coyote.UpgradeProtocol; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import javax.servlet.Filter; import javax.servlet.Servlet; /** * switch classloader to bizClassLoader in pre of web request handler * fix https://github.com/koupleless/koupleless/issues/212 * * please notice: this AutoConfiguration should been loaded by both base and biz, * so the class name should be different in this plugin * * @author lvjing2 * @since 2.2.10 */ @Configuration public class SwitchClassLoaderAutoConfiguration { @Bean(name = "switchClassLoaderFilter") @Order(10) @ConditionalOnClass(value = { Servlet.class, Tomcat.class, UpgradeProtocol.class }, name = { "com.alipay.sofa.ark.springboot1.web.SwitchClassLoaderFilter", "com.alipay.sofa.ark.web.embed.tomcat.ArkTomcatEmbeddedWebappClassLoader", "org.springframework.boot.web.servlet.server.ServletWebServerFactory", "org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration" }) @ConditionalOnMissingClass("org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer") public Filter switchClassLoaderFilter1() { try { Class clazz = Class .forName("com.alipay.sofa.ark.springboot1.web.SwitchClassLoaderFilter"); return (Filter) clazz.newInstance(); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } @Bean(name = "switchClassLoaderFilter") @Order(10) @ConditionalOnClass(value = { Servlet.class, Tomcat.class, UpgradeProtocol.class }, name = { "com.alipay.sofa.ark.springboot2.web.SwitchClassLoaderFilter", "com.alipay.sofa.ark.web.embed.tomcat.ArkTomcatEmbeddedWebappClassLoader", "org.springframework.boot.web.servlet.server.ServletWebServerFactory", "org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration", "org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer" }) public Filter switchClassLoaderFilter2() { try { Class clazz = Class .forName("com.alipay.sofa.ark.springboot2.web.SwitchClassLoaderFilter"); return (Filter) clazz.newInstance(); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } } ================================================ FILE: sofa-ark-plugin/web-ark-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.alipay.sofa.ark.web.embed.tomcat.SwitchClassLoaderAutoConfiguration ================================================ FILE: sofa-ark-plugin/web-ark-plugin/src/main/resources/META-INF/spring.factories ================================================ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.alipay.sofa.ark.web.embed.tomcat.SwitchClassLoaderAutoConfiguration ================================================ FILE: sofa-ark-plugin/web-ark-plugin/src/test/java/com/alipay/sofa/ark/web/embed/WebPluginActivatorTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.web.embed; import com.alipay.sofa.ark.spi.model.PluginContext; import com.alipay.sofa.ark.spi.web.EmbeddedServerService; import org.apache.catalina.LifecycleException; import org.apache.catalina.startup.Tomcat; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.Arrays; import java.util.List; import static org.mockito.Mockito.*; public class WebPluginActivatorTest { private WebPluginActivator webPluginActivator = new WebPluginActivator(); private EmbeddedServerService originalEmbeddedServerService = webPluginActivator.embeddedServerService; @Before public void setUp() { } @After public void tearDown() { webPluginActivator.embeddedServerService = originalEmbeddedServerService; } @Test public void testStartAndStop() throws LifecycleException { EmbeddedServerService embeddedServerService = mock(EmbeddedServerService.class); webPluginActivator.embeddedServerService = embeddedServerService; PluginContext pluginContext = mock(PluginContext.class); webPluginActivator.start(pluginContext); verify(pluginContext, times(1)).publishService(EmbeddedServerService.class, embeddedServerService); Tomcat tomcat = mock(Tomcat.class); List servers = Arrays.asList(tomcat); when(embeddedServerService.iterator()).then(var -> servers.iterator()); webPluginActivator.stop(pluginContext); verify(embeddedServerService, times(1)).iterator(); verify(tomcat, times(1)).destroy(); doThrow(new LifecycleException()).when(tomcat).destroy(); webPluginActivator.stop(pluginContext); verify(embeddedServerService, times(2)).iterator(); verify(tomcat, times(2)).destroy(); when(embeddedServerService.iterator()).thenReturn(Arrays.asList(new Object()).iterator()); webPluginActivator.stop(pluginContext); verify(embeddedServerService, times(3)).iterator(); verify(tomcat, times(2)).destroy(); } } ================================================ FILE: sofa-ark-plugin/web-ark-plugin/src/test/java/com/alipay/sofa/ark/web/embed/tomcat/ArkTomcatEmbeddedWebappClassLoaderTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.web.embed.tomcat; import org.apache.catalina.LifecycleException; import org.apache.catalina.WebResource; import org.apache.catalina.WebResourceRoot; import org.apache.catalina.loader.ResourceEntry; import org.apache.catalina.loader.WebappClassLoaderBase; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.lang.reflect.Field; import java.util.concurrent.ConcurrentHashMap; import static org.apache.catalina.LifecycleState.STARTED; import static org.junit.Assert.*; import static org.mockito.Mockito.*; public class ArkTomcatEmbeddedWebappClassLoaderTest { private ArkTomcatEmbeddedWebappClassLoader arkTomcatEmbeddedWebappClassLoader = new ArkTomcatEmbeddedWebappClassLoader(); public ArkTomcatEmbeddedWebappClassLoaderTest() { } @Before public void setUp() { } @After public void tearDown() { } @Test public void testLoadClass() throws ClassNotFoundException, LifecycleException { // 1) Test load class from current class loader's parent. assertEquals(String.class, arkTomcatEmbeddedWebappClassLoader.loadClass(String.class.getName(), true)); // 2) Test load class from current class loader's parent, which is null. ArkTomcatEmbeddedWebappClassLoader arkTomcatEmbeddedWebappClassLoader2 = new ArkTomcatEmbeddedWebappClassLoader(); try { Field field = WebappClassLoaderBase.class.getDeclaredField("parent"); field.setAccessible(true); field.set(arkTomcatEmbeddedWebappClassLoader2, null); arkTomcatEmbeddedWebappClassLoader2.loadClass(String.class.getName(), true); assertFalse(true); } catch (Exception e) { if (e instanceof ClassNotFoundException) { // we expected ClassNotFoundException is thrown here, so we do nothing. } else { throw new RuntimeException(e); } } // 3) Test load class without web class loader class cache. try { Field field = WebappClassLoaderBase.class.getDeclaredField("state"); field.setAccessible(true); field.set(arkTomcatEmbeddedWebappClassLoader, STARTED); } catch (Exception e) { throw new RuntimeException(e); } WebResourceRoot webResourceRoot = mock(WebResourceRoot.class); WebResource webResource = mock(WebResource.class); String path = "/" + this.getClass().getName().replace('.', '/') + ".class"; when(webResourceRoot.getClassLoaderResource(path)).thenReturn(webResource); arkTomcatEmbeddedWebappClassLoader.setResources(webResourceRoot); assertEquals(this.getClass(), arkTomcatEmbeddedWebappClassLoader.loadClass(this.getClass().getName(), true)); verify(webResourceRoot, times(1)).getClassLoaderResource(path); // note: exists always return false to mock resource not found and fallback to parent class loader. verify(webResource, times(1)).exists(); // 4) Test load class from web class loader class cache. ConcurrentHashMap resourceEntries = new ConcurrentHashMap<>(); path = "/a/b.class"; try { Field field = WebappClassLoaderBase.class.getDeclaredField("resourceEntries"); field.setAccessible(true); ResourceEntry resourceEntry = new ResourceEntry(); resourceEntry.loadedClass = this.getClass(); resourceEntries.put(path, resourceEntry); field.set(arkTomcatEmbeddedWebappClassLoader, resourceEntries); } catch (Exception e) { throw new RuntimeException(e); } assertEquals(this.getClass(), arkTomcatEmbeddedWebappClassLoader.loadClass("a.b", true)); // 5) Test load class with delegate TRUE. arkTomcatEmbeddedWebappClassLoader.setDelegate(true); assertEquals(String.class, arkTomcatEmbeddedWebappClassLoader.loadClass(String.class.getName(), true)); assertEquals(this.getClass(), arkTomcatEmbeddedWebappClassLoader.loadClass("a.b", false)); // cover resolve false // 6) Test load apache class. arkTomcatEmbeddedWebappClassLoader.setDelegate(false); assertEquals(WebappClassLoaderBase.class, arkTomcatEmbeddedWebappClassLoader.loadClass( WebappClassLoaderBase.class.getName(), true)); ResourceEntry resourceEntry = new ResourceEntry(); resourceEntry.loadedClass = String.class; resourceEntries.put("/org/apache.class", resourceEntry); assertEquals(String.class, arkTomcatEmbeddedWebappClassLoader.loadClass("org.apache", false)); // cover resolve false } @Test(expected = ClassNotFoundException.class) public void testLoadClassWithNotFound() throws ClassNotFoundException { assertEquals(this.getClass(), arkTomcatEmbeddedWebappClassLoader.loadClass("a.b", true)); } @Test public void testOtherMethods() throws IOException, ClassNotFoundException { new ArkTomcatEmbeddedWebappClassLoader(this.getClass().getClassLoader()); assertNull(arkTomcatEmbeddedWebappClassLoader.findResource("aaa")); assertEquals(false, arkTomcatEmbeddedWebappClassLoader.findResources("aaa") .hasMoreElements()); arkTomcatEmbeddedWebappClassLoader.addURL(null); try { Field field = WebappClassLoaderBase.class.getDeclaredField("securityManager"); field.setAccessible(true); field.set(arkTomcatEmbeddedWebappClassLoader, new SecurityManager()); } catch (Exception e) { throw new RuntimeException(e); } arkTomcatEmbeddedWebappClassLoader.checkPackageAccess(String.class.getName()); arkTomcatEmbeddedWebappClassLoader.checkPackageAccess("java"); // cover wrong class name } @Test(expected = ClassNotFoundException.class) public void testCheckPackageAccessFailed() throws IOException, ClassNotFoundException { SecurityManager securityManager = mock(SecurityManager.class); doThrow(new SecurityException()).when(securityManager).checkPackageAccess("a"); try { Field field = WebappClassLoaderBase.class.getDeclaredField("securityManager"); field.setAccessible(true); field.set(arkTomcatEmbeddedWebappClassLoader, securityManager); } catch (Exception e) { throw new RuntimeException(e); } arkTomcatEmbeddedWebappClassLoader.checkPackageAccess("a.b"); } } ================================================ FILE: sofa-ark-plugin/web-ark-plugin/src/test/java/com/alipay/sofa/ark/web/embed/tomcat/EmbeddedServerServiceImplTest.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.ark.web.embed.tomcat; import org.apache.catalina.startup.Tomcat; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; public class EmbeddedServerServiceImplTest { private EmbeddedServerServiceImpl embeddedServerServiceImpl = new EmbeddedServerServiceImpl(); @Before public void setUp() { } @After public void tearDown() { } @Test public void testPutEmbedServer() { int port = 8080; assertEquals(false, embeddedServerServiceImpl.putEmbedServer(port, null)); assertEquals(null, embeddedServerServiceImpl.getEmbedServer(port)); Tomcat tomcat = new Tomcat(); embeddedServerServiceImpl.putEmbedServer(port, tomcat); assertEquals(tomcat, embeddedServerServiceImpl.getEmbedServer(port)); Tomcat tomcat2 = new Tomcat(); embeddedServerServiceImpl.putEmbedServer(port, tomcat2); // should be still old tomcat assertEquals(tomcat, embeddedServerServiceImpl.getEmbedServer(port)); // New test case for edge case int newPort = 9090; embeddedServerServiceImpl.putEmbedServer(newPort, tomcat2); assertEquals(tomcat2, embeddedServerServiceImpl.getEmbedServer(newPort)); assertEquals(tomcat, embeddedServerServiceImpl.getEmbedServer(port)); } }