├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── osgi-spring-bundle-scanner ├── .gitignore ├── bnd.bnd ├── osgi-spring-bundle-scanner.gradle └── src │ └── main │ ├── java │ └── com │ │ └── example │ │ └── osgi │ │ └── spring │ │ └── scanner │ │ ├── annotation │ │ ├── export │ │ │ └── ExportAsService.java │ │ └── imports │ │ │ └── ComponentImport.java │ │ ├── extension │ │ ├── ClassIndexBeanDefinitionScanner.java │ │ ├── ComponentImportBeanFactoryPostProcessor.java │ │ ├── ExportedSeviceManager.java │ │ ├── OSGIBundleScannerBeanDefinitionParser.java │ │ ├── OSGIBundleScannerNamespaceHandler.java │ │ └── ServiceExporterBeanPostProcessor.java │ │ ├── processor │ │ ├── ComponentAnnotationProcessor.java │ │ ├── ComponentImportAnnotationProcessor.java │ │ └── IndexWritingAnnotationProcessor.java │ │ └── util │ │ ├── AnnotationIndexReader.java │ │ └── ClassIndexFiles.java │ └── resources │ ├── META-INF │ ├── services │ │ └── javax.annotation.processing.Processor │ ├── spring.handlers │ └── spring.schemas │ └── com │ └── example │ └── osgi │ └── spring │ └── scanner │ └── osgi-bundle-scanner.xsd ├── osgi-spring-extender ├── bnd.bnd ├── osgi-spring-extender.gradle └── src │ └── main │ ├── java │ ├── com │ │ └── example │ │ │ └── osgi │ │ │ └── spring │ │ │ └── extender │ │ │ ├── NonValidatingOsgiApplicationContextCreator.java │ │ │ ├── ThreadPoolAsyncTaskExecutor.java │ │ │ └── external │ │ │ └── ApplicationContextPreProcessor.java │ └── org │ │ └── eclipse │ │ └── gemini │ │ └── blueprint │ │ └── context │ │ └── support │ │ └── NonValidatingOsgiBundleXmlApplicationContext.java │ └── resources │ └── META-INF │ └── spring │ └── extender │ └── spring-event-bridge.xml ├── osgi-spring-wired-test-bundle ├── bnd.bnd ├── osgi-spring-wired-test-bundle.gradle └── src │ └── main │ └── resources │ ├── META-INF │ └── spring │ │ └── spring-scanner.xml │ └── templates │ └── index.html ├── settings.gradle └── src ├── main ├── java │ └── com │ │ └── example │ │ └── osgi │ │ └── framework │ │ ├── App.java │ │ ├── felix │ │ ├── FelixService.java │ │ └── SpringAwareFelixBundleListener.java │ │ ├── util │ │ └── SpringPropertiesHelper.java │ │ └── web │ │ ├── DispatcherSetup.java │ │ ├── MainErrorController.java │ │ └── OsgiBundleXmlWebApplicationContext.java ├── resources │ ├── application-dev.properties │ ├── application-prod.properties │ ├── banner.txt │ ├── example.felix.properties │ ├── log4j-dev.xml │ ├── log4j.xml │ ├── messages_en.properties │ └── templates │ │ ├── error-404.html │ │ └── error-500.html └── templates │ └── start.sh.template └── test └── java └── com └── example └── osgi └── framework └── tests └── FelixServiceTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build* 3 | **/build* 4 | !gradle/ 5 | temp 6 | .classpath 7 | .project 8 | org.eclipse* 9 | 10 | bin 11 | bundled_plugins 12 | felix_cache 13 | felix-cache 14 | plugins 15 | 16 | log4j2.out.xml 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example Spring Boot web application running embedded Apache Felix OSGI Container 2 | 3 | >> 4 | The idea is to be able to: 5 | * load basic Spring Boot App running an embedded Felix Framework. 6 | * Add functionality using plain jars using standard annotations like Spring Web controllers and ServletFilter etc 7 | * Auto detect and serve embedded static resources 8 | 9 | 10 | ## Spring boot 11 | - [x] Create App Entrypoint as @SpringBootApplication 12 | - [x] Customise Spring Boot start Banner 13 | - [x] Configure Spring default logging to use log4j2.xml in resources 14 | - [x] Add a way for Felix framework to use Spring boot properties in internal environment 15 | 16 | ## Felix 17 | - [x] Create FelixService as Spring Service Component and ApplicationReadyEvent Event Listener to initialise it 18 | - [x] Configure Felix to load some predefined framework bundles 19 | 20 | 21 | ## Gradle 22 | - [x] Generate custom start.sh, start.bat script in output distribution 23 | - [x] Create Task to copy configurable OSGI Framework bundles into framework_bundles folder in distribution 24 | - [x] Generate an empty plugins folder in the distribution 25 | - [x] Generate zip and tar.gz distributions which include the ./framework_bundles and ./plugins folder plus start scripts. 26 | 27 | ## Additional Custom Bundles for initial framework load 28 | - [ ] PluginServices 29 | - [ ] Services for turning plain JAR into OSGI bundle using BND tools 30 | - [ ] OSGI bundle activation processing pipeline with hooks for custom processing e.g. 31 | - Detecting and wiring Servlets, Filters via annotations 32 | - Detecting and wiring Spring MVC Controllers, RestControllers etc 33 | - Detecting and wiring custom @AuthRequired annotations etc 34 | - Detecting static resources 35 | 36 | - [ ] PluginFileWatcherService - watch a dir for new jars then use PluginServices to process it. -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import org.apache.tools.ant.filters.ReplaceTokens 2 | 3 | buildscript { 4 | ext { 5 | springBootVersion = '2.0.3.RELEASE' 6 | springDependencyManagementVersion = '1.0.6.RELEASE' 7 | } 8 | repositories { 9 | mavenCentral() 10 | } 11 | dependencies { 12 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 13 | } 14 | } 15 | 16 | plugins { 17 | id 'java' apply true 18 | id 'eclipse' apply true 19 | id 'org.springframework.boot' version '2.0.3.RELEASE' apply true 20 | id 'io.spring.dependency-management' version '1.0.6.RELEASE' apply true 21 | id 'application' apply true 22 | } 23 | 24 | defaultTasks 'fatJarTar','fatJarZip' 25 | 26 | repositories { 27 | mavenCentral() 28 | maven { url "https://maven.atlassian.com/content/repositories/atlassian-public/" } 29 | maven { url "https://repo.spring.io/libs-release" } 30 | maven { url "https://repo.spring.io/plugins-release" } 31 | } 32 | 33 | sourceCompatibility = 1.8 34 | group = 'com.example.osgi.framework' 35 | version = '0.0.1' 36 | mainClassName = 'com.example.osgi.framework.App' 37 | 38 | configurations { 39 | framework_bundles 40 | plugin_bundles 41 | } 42 | 43 | configurations.framework_bundles{ 44 | transitive = false 45 | } 46 | 47 | configurations.plugin_bundles{ 48 | transitive = false 49 | } 50 | 51 | eclipse.project { 52 | natures 'org.springsource.ide.eclipse.gradle.core.nature' 53 | } 54 | 55 | bootRun { 56 | systemProperty('spring.profiles.active', 'dev') 57 | } 58 | 59 | bootJar { 60 | launchScript() 61 | } 62 | 63 | dependencies { 64 | // Required for Spring Boot 65 | compile("org.springframework.boot:spring-boot-starter-web:2.0.3.RELEASE") 66 | compile "org.springframework:spring-webmvc:5.0.7.RELEASE" 67 | compile("org.springframework.boot:spring-boot-starter-thymeleaf:2.0.3.RELEASE") 68 | compile('org.apache.felix:org.apache.felix.framework:6.0.0') 69 | compile('org.apache.felix:org.apache.felix.main:6.0.0') 70 | compileOnly "org.eclipse.gemini.blueprint:gemini-blueprint-core:3.0.0.M01" 71 | compileOnly "org.eclipse.gemini.blueprint:gemini-blueprint-io:3.0.0.M01" 72 | compileOnly "org.eclipse.gemini.blueprint:gemini-blueprint-extender:3.0.0.M01" 73 | 74 | // Custom spring scanner 75 | //compile(project(":osgi-spring-bundle-scanner")) 76 | 77 | testCompile('org.springframework.boot:spring-boot-starter-test') 78 | 79 | // Custom Spring integration 80 | framework_bundles project(":osgi-spring-extender") 81 | plugin_bundles project(":osgi-spring-bundle-scanner") 82 | 83 | // For testing 84 | plugin_bundles project(":osgi-spring-wired-test-bundle") 85 | 86 | // Required for Gemini Blueprint 87 | framework_bundles "org.eclipse.gemini.blueprint:gemini-blueprint-core:3.0.0.M01" 88 | framework_bundles "org.eclipse.gemini.blueprint:gemini-blueprint-io:3.0.0.M01" 89 | framework_bundles "org.eclipse.gemini.blueprint:gemini-blueprint-extender:3.0.0.M01" 90 | //framework_bundles "org.eclipse.gemini.blueprint:gemini-blueprint-extensions:3.0.0.M01" 91 | framework_bundles "org.apache.servicemix.bundles:org.apache.servicemix.bundles.aopalliance:1.0_6" 92 | framework_bundles "javax.validation:validation-api:2.0.1.Final" 93 | framework_bundles "javax.servlet:javax.servlet-api:4.0.1" 94 | framework_bundles "commons-logging:commons-logging:1.2" 95 | 96 | // Required for Spring in bundles 97 | framework_bundles "org.apache.servicemix.bundles:org.apache.servicemix.bundles.spring-aop:5.0.7.RELEASE_1" 98 | framework_bundles "org.apache.servicemix.bundles:org.apache.servicemix.bundles.spring-beans:5.0.7.RELEASE_1" 99 | framework_bundles "org.apache.servicemix.bundles:org.apache.servicemix.bundles.spring-context:5.0.7.RELEASE_1" 100 | framework_bundles "org.apache.servicemix.bundles:org.apache.servicemix.bundles.spring-context-support:5.0.7.RELEASE_1" 101 | framework_bundles "org.apache.servicemix.bundles:org.apache.servicemix.bundles.spring-core:5.0.7.RELEASE_1" 102 | framework_bundles "org.apache.servicemix.bundles:org.apache.servicemix.bundles.spring-expression:5.0.7.RELEASE_1" 103 | framework_bundles "org.apache.servicemix.bundles:org.apache.servicemix.bundles.spring-web:5.0.7.RELEASE_1" 104 | framework_bundles "org.apache.servicemix.bundles:org.apache.servicemix.bundles.spring-webmvc:5.0.7.RELEASE_1" 105 | 106 | // Felix Fileinstall for monitoring a directory and installing / updating bundles 107 | framework_bundles "org.apache.felix:org.apache.felix.fileinstall:3.6.4" 108 | framework_bundles "org.apache.felix:org.apache.felix.eventadmin:1.5.0" 109 | 110 | //framework_bundles("com.atlassian.plugin:atlassian-spring-scanner-runtime:1.2.12") 111 | 112 | //framework_bundles "org.apache.felix:org.apache.felix.configadmin:1.9.2" 113 | //framework_bundles "org.apache.felix:org.apache.felix.scr:2.1.0" 114 | //framework_bundles "org.apache.felix:org.apache.felix.http.jetty:4.0.0" 115 | //framework_bundles "org.apache.felix:org.apache.felix.http.servlet-api:1.1.2" 116 | //framework_bundles "org.apache.servicemix:servicemix-core:3.4.1" 117 | //framework_bundles "org.apache.servicemix.bundles:org.apache.servicemix.bundles.commons-io:1.4_3" 118 | //framework_bundles "org.apache.felix:org.apache.felix.webconsole:4.3.4" 119 | //framework_bundles "org.apache.felix:org.apache.felix.gogo.command:1.0.2" 120 | //framework_bundles "org.apache.felix:org.apache.felix.gogo.runtime:1.1.0" 121 | //framework_bundles "org.apache.felix:org.apache.felix.gogo.shell:1.1.0" 122 | } 123 | 124 | task cleanBundleFolders(type: Delete) { 125 | delete "$buildDir/framework_bundles" 126 | delete "$buildDir/plugins" 127 | followSymlinks = false 128 | } 129 | cleanBundleFolders.group = "custom" 130 | 131 | task copyFrameworkBundlesToBuildFolder(type: Copy) { 132 | into "$buildDir/framework_bundles" 133 | from configurations.framework_bundles 134 | } 135 | copyFrameworkBundlesToBuildFolder.group = "custom" 136 | copyFrameworkBundlesToBuildFolder.dependsOn(cleanBundleFolders) 137 | 138 | task copyPluginBundlesToBuildFolder(type: Copy) { 139 | into "$buildDir/plugins" 140 | from configurations.plugin_bundles 141 | } 142 | copyPluginBundlesToBuildFolder.group = "custom" 143 | copyPluginBundlesToBuildFolder.dependsOn(copyFrameworkBundlesToBuildFolder) 144 | 145 | bootRun.dependsOn(copyPluginBundlesToBuildFolder) 146 | 147 | def distributionsCopySpec = copySpec { 148 | 149 | def distroName = "$name-$version"; 150 | 151 | into(distroName) { 152 | 153 | new File(buildDir, "$distroName/plugins").mkdirs() 154 | new File(buildDir, "$distroName/bundled_plugins").mkdirs() 155 | new File(buildDir, "$distroName/conf").mkdirs() 156 | new File(buildDir, "$distroName/bin").mkdirs() 157 | new File(buildDir, "$distroName/lib").mkdirs() 158 | 159 | from file("${buildDir}/$distroName") 160 | into file("/") 161 | 162 | into('lib') { 163 | from libsDir 164 | include '*.jar' //copy the fat jar created by bootRepackage 165 | } 166 | into('framework_bundles') { 167 | from configurations.framework_bundles 168 | } 169 | into('bin') { 170 | from 'src/main/templates' 171 | include 'start.*.template' 172 | filter(ReplaceTokens, tokens: [ 173 | name: project.name, 174 | extra_java_opts_property: project.name.replace('-','_').toUpperCase() + "_OPTS", 175 | extra_java_opts: '-Dspring.config.location=$APP_HOME/conf/ -Dspring.profiles.active=prod', 176 | version: project.version, 177 | generated: new Date().toString() 178 | ]) 179 | fileMode = 0777 180 | } 181 | into('conf') { 182 | from "src/main/resources/application.properties" //custom start script based on startScripts task output 183 | } 184 | rename ("^start.sh.template\$","start.sh") 185 | rename ("^start.bat.template\$","start.bat") 186 | 187 | } 188 | } 189 | 190 | 191 | task fatJarZip(type: Zip, dependsOn: 'assembleBootDist') { with distributionsCopySpec } 192 | task fatJarTar(type: Tar, dependsOn: 'assembleBootDist') { with distributionsCopySpec } 193 | fatJarZip.group = "custom" 194 | fatJarTar.group = "custom" 195 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoosername/example-spring-boot-embedded-felix/5e016960385b4e2277804a43aa99c3567698bcf7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-bin.zip 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/bnd.bnd: -------------------------------------------------------------------------------- 1 | -dsannotations: 2 | Bundle-ManifestVersion: 2 3 | Export-Package: \ 4 | com.example.osgi.spring.scanner.annotation.export,\ 5 | com.example.osgi.spring.scanner.annotation.imports,\ 6 | com.example.osgi.spring.scanner.extension,\ 7 | com.example.osgi.spring.scanner.processor,\ 8 | com.example.osgi.spring.scanner.util, \ 9 | com.example.osgi.spring.scanner 10 | Bundle-Version: 0.0.1 11 | Fragment-Host: org.eclipse.gemini.blueprint.extender 12 | Import-Package: \ 13 | com.example.osgi.spring.extender.external.*,\ 14 | org.springframework.beans.factory.xml.*,\ 15 | * 16 | Bundle-Name: osgi-spring-bundle-scanner 17 | Bundle-Description: Provides custom Spring scanner which scans OSGI bundles for beans and preprocesses \ 18 | for common annotations -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/osgi-spring-bundle-scanner.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath 'biz.aQute.bnd:biz.aQute.bnd.gradle:4.0.0' 7 | } 8 | } 9 | 10 | plugins { 11 | id 'java' apply true 12 | id 'eclipse' apply true 13 | } 14 | 15 | apply plugin: 'biz.aQute.bnd.builder' 16 | 17 | repositories { 18 | mavenCentral() 19 | maven { url "https://maven.atlassian.com/content/repositories/atlassian-public/" } 20 | maven { url "https://repo.spring.io/libs-release" } 21 | maven { url "https://repo.spring.io/plugins-release" } 22 | } 23 | 24 | sourceCompatibility = 1.8 25 | group = 'com.example.osgi.spring.scanner' 26 | version = '0.0.1' 27 | 28 | eclipse.project { 29 | natures 'org.springsource.ide.eclipse.gradle.core.nature' 30 | } 31 | 32 | dependencies { 33 | 34 | compileOnly 'org.apache.felix:org.apache.felix.framework:6.0.0' 35 | compileOnly "org.eclipse.gemini.blueprint:gemini-blueprint-core:3.0.0.M01" 36 | 37 | compileOnly("org.springframework:spring-core:5.0.7.RELEASE") 38 | compileOnly("org.springframework:spring-aop:5.0.7.RELEASE") 39 | compileOnly("org.springframework:spring-beans:5.0.7.RELEASE") 40 | compileOnly("org.springframework:spring-context:5.0.7.RELEASE") 41 | 42 | //compileOnly("org.springframework.osgi:spring-osgi-core:1.2.1") 43 | compile("commons-lang:commons-lang:2.6") 44 | compile("com.google.guava:guava:25.1-jre") 45 | 46 | testCompile("junit:junit:4.12") 47 | testCompile("org.mokito:mockito-all:1.10.19") 48 | } -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/java/com/example/osgi/spring/scanner/annotation/export/ExportAsService.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.scanner.annotation.export; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Denotes a component to be exported as an OSGi service available to other bundles. 10 | */ 11 | @Target(ElementType.TYPE) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface ExportAsService 14 | { 15 | /** 16 | * the interfaces the service should be exported as. 17 | * if not specified, the interfaces will be calculated using reflection 18 | * @return 19 | */ 20 | Class[] value() default {}; 21 | } 22 | -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/java/com/example/osgi/spring/scanner/annotation/imports/ComponentImport.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.scanner.annotation.imports; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | /** 8 | * Annotation representing an OSGi service that's required to be imported into this bundle. 9 | * Can be applied to constructor params where the param type is a service interface exported 10 | * by another bundle 11 | */ 12 | @Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface ComponentImport 15 | { 16 | String value() default ""; 17 | } 18 | -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/java/com/example/osgi/spring/scanner/extension/ClassIndexBeanDefinitionScanner.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.scanner.extension; 2 | 3 | import static com.example.osgi.spring.scanner.util.AnnotationIndexReader.readAllIndexFilesForProduct; 4 | import static com.example.osgi.spring.scanner.util.AnnotationIndexReader.readIndexFile; 5 | 6 | import java.beans.Introspector; 7 | import java.util.*; 8 | 9 | import org.apache.commons.lang.StringUtils; 10 | import org.apache.commons.logging.Log; 11 | import org.apache.commons.logging.LogFactory; 12 | import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; 13 | import org.springframework.beans.factory.config.BeanDefinition; 14 | import org.springframework.beans.factory.config.BeanDefinitionHolder; 15 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; 16 | import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; 17 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 18 | import org.springframework.util.ClassUtils; 19 | 20 | import com.example.osgi.spring.scanner.util.ClassIndexFiles; 21 | 22 | /** 23 | * This class is responsible for reading the class index files and generating bean definitions from them. 24 | * We assume all of the proper type checks/visibility checks have been done by the annotation processors. 25 | * This means that if a class is listed in an index, it qualifies as a bean candidate. 26 | */ 27 | public class ClassIndexBeanDefinitionScanner 28 | { 29 | protected final Log logger = LogFactory.getLog(getClass()); 30 | 31 | private BeanDefinitionRegistry registry; 32 | 33 | public ClassIndexBeanDefinitionScanner(BeanDefinitionRegistry registry) 34 | { 35 | this.registry = registry; 36 | } 37 | 38 | /** 39 | * Gets the map of beanName -> beanDefinition and returns a set of bean definition holders 40 | * @return 41 | */ 42 | protected Set doScan() 43 | { 44 | 45 | Set beanDefinitions = new LinkedHashSet(); 46 | Map namesAndDefinitions = findCandidateComponents(); 47 | 48 | for (Map.Entry nameAndDefinition : namesAndDefinitions.entrySet()) 49 | { 50 | 51 | if (checkCandidate(nameAndDefinition.getKey(), nameAndDefinition.getValue())) 52 | { 53 | BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(nameAndDefinition.getValue(), nameAndDefinition.getKey()); 54 | beanDefinitions.add(definitionHolder); 55 | registerBeanDefinition(definitionHolder, registry); 56 | } 57 | } 58 | return beanDefinitions; 59 | } 60 | 61 | 62 | /** 63 | * Reads the components from the index file(s) and generates a map of beanName -> beanDfinitions for them 64 | * @return 65 | */ 66 | public Map findCandidateComponents() 67 | { 68 | Map candidates = new HashMap(); 69 | 70 | List beanTypeAndNames = readAllIndexFilesForProduct(ClassIndexFiles.COMPONENT_INDEX_FILE, Thread.currentThread().getContextClassLoader()); 71 | 72 | for (String beanTypeAndName : beanTypeAndNames) 73 | { 74 | String[] typeAndName = StringUtils.split(beanTypeAndName, "#"); 75 | 76 | String beanClassname = typeAndName[0]; 77 | String beanName = ""; 78 | 79 | if (typeAndName.length > 1) 80 | { 81 | beanName = typeAndName[1]; 82 | } 83 | 84 | if (StringUtils.isBlank(beanName)) 85 | { 86 | beanName = Introspector.decapitalize(ClassUtils.getShortName(beanClassname)); 87 | } 88 | 89 | candidates.put(beanName, BeanDefinitionBuilder.genericBeanDefinition(beanClassname).getBeanDefinition()); 90 | } 91 | 92 | return candidates; 93 | } 94 | 95 | /** 96 | * copyPasta from spring-context:component-scan classes 97 | */ 98 | protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) 99 | { 100 | BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry); 101 | } 102 | 103 | /** 104 | * copyPasta from spring-context:component-scan classes 105 | * 106 | * Check the given candidate's bean name, determining whether the corresponding 107 | * bean definition needs to be registered or conflicts with an existing definition. 108 | */ 109 | protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException 110 | { 111 | if (!this.registry.containsBeanDefinition(beanName)) 112 | { 113 | return true; 114 | } 115 | 116 | BeanDefinition existingDef = this.registry.getBeanDefinition(beanName); 117 | BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition(); 118 | if (originatingDef != null) 119 | { 120 | existingDef = originatingDef; 121 | } 122 | if (isCompatible(beanDefinition, existingDef)) 123 | { 124 | return false; 125 | } 126 | throw new IllegalStateException("Annotation-specified bean name '" + beanName + 127 | "' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " + 128 | "non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]"); 129 | } 130 | 131 | /** 132 | * copyPasta from spring-context:component-scan classes 133 | * 134 | * Determine whether the given new bean definition is compatible with 135 | * the given existing bean definition. 136 | *

The default implementation simply considers them as compatible 137 | * when the bean class name matches. 138 | * 139 | */ 140 | protected boolean isCompatible(BeanDefinition newDefinition, BeanDefinition existingDefinition) 141 | { 142 | return (!(existingDefinition instanceof AnnotatedBeanDefinition) || // explicitly registered overriding bean 143 | newDefinition.getSource().equals(existingDefinition.getSource()) || // scanned same file twice 144 | newDefinition.equals(existingDefinition)); // scanned equivalent class twice 145 | } 146 | 147 | 148 | } 149 | -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/java/com/example/osgi/spring/scanner/extension/ComponentImportBeanFactoryPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.scanner.extension; 2 | 3 | import static com.example.osgi.spring.scanner.util.AnnotationIndexReader.readAllIndexFilesForProduct; 4 | 5 | import java.util.List; 6 | 7 | import org.apache.commons.lang.StringUtils; 8 | import org.eclipse.gemini.blueprint.service.importer.support.OsgiServiceProxyFactoryBean; 9 | import org.osgi.framework.Bundle; 10 | import org.osgi.framework.BundleContext; 11 | import org.springframework.beans.BeansException; 12 | import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 13 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 14 | import org.springframework.beans.factory.support.AbstractBeanDefinition; 15 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; 16 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 17 | 18 | import com.example.osgi.spring.scanner.util.ClassIndexFiles; 19 | 20 | /** 21 | * This class is run after all of the bean definitions have been gathered for the current bundle. 22 | * It looks for any of the *Import annotations and registers the proper OSGi imports. 23 | * This is a BeanFactoryPostProcessor because it needs to run before the beans are created 24 | * so that the services are available when spring wires up the beans. 25 | */ 26 | public class ComponentImportBeanFactoryPostProcessor implements BeanFactoryPostProcessor 27 | { 28 | private final BundleContext bundleContext; 29 | 30 | public ComponentImportBeanFactoryPostProcessor(BundleContext bundleContext) 31 | { 32 | this.bundleContext = bundleContext; 33 | } 34 | 35 | /** 36 | * Reads the componentimport inex file(s) and registers the bean wrappers that represent OSGi import services 37 | * @param beanFactory 38 | * @throws BeansException 39 | */ 40 | @Override 41 | public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException 42 | { 43 | BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; 44 | 45 | Bundle bundle = bundleContext.getBundle(); 46 | List classNames = readAllIndexFilesForProduct(ClassIndexFiles.COMPONENT_IMPORT_INDEX_FILE, bundle); 47 | 48 | for (String className : classNames) 49 | { 50 | String[] typeAndName = StringUtils.split(className, "#"); 51 | String beanType = typeAndName[0]; 52 | String beanName = (typeAndName.length > 1) ? typeAndName[1] : ""; 53 | 54 | try 55 | { 56 | Class beanClass = beanFactory.getBeanClassLoader().loadClass(beanType); 57 | registerComponentImportBean(registry, beanClass, beanName); 58 | } 59 | catch (ClassNotFoundException e) 60 | { 61 | //ignore 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * Figures out the proper bean name for the service and registers it 68 | * @param registry 69 | * @param paramType 70 | * @param beanName 71 | */ 72 | private void registerComponentImportBean(BeanDefinitionRegistry registry, Class paramType, String beanName) 73 | { 74 | String serviceBeanName = beanName; 75 | 76 | if ("".equals(serviceBeanName)) 77 | { 78 | 79 | serviceBeanName = StringUtils.uncapitalize(paramType.getSimpleName()); 80 | } 81 | 82 | registerBeanDefinition(registry, serviceBeanName, "", paramType); 83 | } 84 | 85 | /** 86 | * Creates an OsgiServiceProxyFactoryBean for the requested import type. 87 | * 88 | * @param registry 89 | * @param beanName 90 | * @param filter 91 | * @param interfaces 92 | */ 93 | private void registerBeanDefinition(BeanDefinitionRegistry registry, String beanName, String filter, Class interfaces) 94 | { 95 | 96 | BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(OsgiServiceProxyFactoryBean.class); 97 | builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR); 98 | builder.setLazyInit(true); 99 | 100 | if(StringUtils.isNotBlank(filter)) 101 | { 102 | builder.addPropertyValue("filter", filter); 103 | } 104 | 105 | builder.addPropertyValue("interfaces", new Class[]{interfaces}); 106 | registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); 107 | } 108 | 109 | 110 | } 111 | -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/java/com/example/osgi/spring/scanner/extension/ExportedSeviceManager.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.scanner.extension; 2 | 3 | import java.util.Hashtable; 4 | import java.util.Map; 5 | 6 | import org.eclipse.gemini.blueprint.service.exporter.support.ExportContextClassLoaderEnum; 7 | import org.eclipse.gemini.blueprint.service.exporter.support.OsgiServiceFactoryBean; 8 | import org.osgi.framework.BundleContext; 9 | import org.osgi.framework.ServiceRegistration; 10 | 11 | /** 12 | * Utility class to encapsulate the registration/de-registration of OSGi services exported by public components 13 | */ 14 | public class ExportedSeviceManager 15 | { 16 | private final Hashtable exporters; 17 | 18 | public ExportedSeviceManager() { 19 | this.exporters = new Hashtable(); 20 | } 21 | 22 | /** 23 | * Exports a component as an OSGi service for use by other bundles 24 | * @param bundleContext 25 | * @param bean 26 | * @param beanName 27 | * @param serviceProps 28 | * @param autoExport 29 | * @param interfaces 30 | * @return 31 | * @throws Exception 32 | */ 33 | public ServiceRegistration registerService(final BundleContext bundleContext, final Object bean, final String beanName, final Map serviceProps, final Class... interfaces) throws Exception { 34 | 35 | serviceProps.put("org.springframework.osgi.bean.name",beanName); 36 | 37 | OsgiServiceFactoryBean osgiExporter = createExporter(bundleContext,bean,beanName,serviceProps,interfaces); 38 | 39 | int hashCode = System.identityHashCode(bean); 40 | exporters.put(hashCode,osgiExporter); 41 | 42 | ServiceRegistration reg = (ServiceRegistration) osgiExporter.getObject(); 43 | 44 | return reg; 45 | } 46 | 47 | /** 48 | * de-registers an OSGi service 49 | * @param bundleContext 50 | * @param bean 51 | */ 52 | public void unregisterService(BundleContext bundleContext, Object bean) { 53 | int hashCode = System.identityHashCode(bean); 54 | OsgiServiceFactoryBean exporter = exporters.get(hashCode); 55 | if(null != exporter) 56 | { 57 | exporter.destroy(); 58 | exporters.remove(hashCode); 59 | } 60 | } 61 | 62 | /** 63 | * creates the OsgiServiceFactoryBean used by spring when registering services 64 | */ 65 | private OsgiServiceFactoryBean createExporter(final BundleContext bundleContext, final Object bean, final String beanName, final Map serviceProps, final Class... interfaces) throws Exception { 66 | 67 | OsgiServiceFactoryBean exporter = new OsgiServiceFactoryBean(); 68 | exporter.setBeanClassLoader(bean.getClass().getClassLoader()); 69 | exporter.setBeanName(beanName); 70 | exporter.setBundleContext(bundleContext); 71 | exporter.setExportContextClassLoader(ExportContextClassLoaderEnum.UNMANAGED); 72 | exporter.setInterfaces(interfaces); 73 | exporter.setServiceProperties(serviceProps); 74 | exporter.setTarget(bean); 75 | exporter.afterPropertiesSet(); 76 | 77 | return exporter; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/java/com/example/osgi/spring/scanner/extension/OSGIBundleScannerBeanDefinitionParser.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.scanner.extension; 2 | 3 | import java.util.*; 4 | 5 | import org.springframework.beans.MutablePropertyValues; 6 | import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; 7 | import org.springframework.beans.factory.config.BeanDefinition; 8 | import org.springframework.beans.factory.config.BeanDefinitionHolder; 9 | import org.springframework.beans.factory.parsing.BeanComponentDefinition; 10 | import org.springframework.beans.factory.parsing.CompositeComponentDefinition; 11 | import org.springframework.beans.factory.support.AbstractBeanDefinition; 12 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 13 | import org.springframework.beans.factory.support.RootBeanDefinition; 14 | import org.springframework.beans.factory.xml.BeanDefinitionParser; 15 | import org.springframework.beans.factory.xml.ParserContext; 16 | import org.springframework.beans.factory.xml.XmlReaderContext; 17 | import org.springframework.context.annotation.AnnotationConfigUtils; 18 | import org.springframework.util.ClassUtils; 19 | import org.w3c.dom.Element; 20 | 21 | /** 22 | * This class is responsible for handling the "parsing" of the scan-indexes element in the spring beans file. 23 | * Ultimately, this is what kicks off the index scanner and is the starting point for registering bean definitions 24 | */ 25 | public class OSGIBundleScannerBeanDefinitionParser implements BeanDefinitionParser 26 | { 27 | 28 | public static final String JAVAX_INJECT_CLASSNAME = "javax.inject.Inject"; 29 | 30 | @Override 31 | public BeanDefinition parse(Element element, ParserContext parserContext) 32 | { 33 | // Actually scan for bean definitions and register them. 34 | ClassIndexBeanDefinitionScanner scanner = new ClassIndexBeanDefinitionScanner(parserContext.getReaderContext().getRegistry()); 35 | Set beanDefinitions = scanner.doScan(); 36 | 37 | registerComponents(parserContext.getReaderContext(), beanDefinitions, element); 38 | 39 | return null; 40 | } 41 | 42 | /** 43 | * Takes the scanned bean definitions and adds them to a root copmonent. 44 | * Also adds in the post-processors required to import/export OSGi services. 45 | * Finally fires the component registered event with our root component. 46 | * @param readerContext 47 | * @param beanDefinitions 48 | * @param element 49 | */ 50 | protected void registerComponents(XmlReaderContext readerContext, Set beanDefinitions, Element element) 51 | { 52 | Object source = readerContext.extractSource(element); 53 | CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source); 54 | 55 | //Add the beans we found to our root component 56 | for (Iterator it = beanDefinitions.iterator(); it.hasNext(); ) 57 | { 58 | BeanDefinitionHolder beanDefHolder = (BeanDefinitionHolder) it.next(); 59 | compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder)); 60 | } 61 | 62 | //add our custom post-processors along with the standard @Autowired processor 63 | Set processorDefinitions = new LinkedHashSet(); 64 | processorDefinitions.addAll(AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source)); 65 | 66 | //Let's be nice and support javax.inject.Inject annotations 67 | BeanDefinitionHolder javaxInject = getJavaxInjectPostProcessor(readerContext.getRegistry(), source); 68 | if(null != javaxInject) 69 | { 70 | processorDefinitions.add(javaxInject); 71 | } 72 | processorDefinitions.add(getComponentImportPostProcessor(readerContext.getRegistry(), source)); 73 | processorDefinitions.add(getServiceExportPostProcessor(readerContext.getRegistry(), source)); 74 | 75 | for (BeanDefinitionHolder processorDefinition : processorDefinitions) 76 | { 77 | compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition)); 78 | } 79 | 80 | readerContext.fireComponentRegistered(compositeDef); 81 | } 82 | 83 | private BeanDefinitionHolder getJavaxInjectPostProcessor(BeanDefinitionRegistry registry, Object source) 84 | { 85 | if(ClassUtils.isPresent(JAVAX_INJECT_CLASSNAME, getClass().getClassLoader())) 86 | { 87 | try 88 | { 89 | Class injectClass = getClass().getClassLoader().loadClass(JAVAX_INJECT_CLASSNAME); 90 | Map properties = new HashMap(); 91 | properties.put("autowiredAnnotationType",injectClass); 92 | 93 | RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class); 94 | def.setSource(source); 95 | def.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); 96 | def.setPropertyValues(new MutablePropertyValues(properties)); 97 | 98 | return registerBeanPostProcessor(registry, def, "javaxInjectBeanPostProcessor"); 99 | } 100 | catch (ClassNotFoundException e) 101 | { 102 | //ignore 103 | } 104 | } 105 | 106 | return null; 107 | } 108 | 109 | /** 110 | * Helper to convert a post-processor into a proper holder 111 | * @param registry 112 | * @param definition 113 | * @param beanName 114 | * @return 115 | */ 116 | private BeanDefinitionHolder registerBeanPostProcessor(BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) 117 | { 118 | definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); 119 | 120 | registry.registerBeanDefinition(beanName, definition); 121 | return new BeanDefinitionHolder(definition, beanName); 122 | } 123 | 124 | private BeanDefinitionHolder getComponentImportPostProcessor(BeanDefinitionRegistry registry, Object source) 125 | { 126 | RootBeanDefinition def = new RootBeanDefinition(ComponentImportBeanFactoryPostProcessor.class); 127 | def.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR); 128 | def.setSource(source); 129 | 130 | return registerBeanPostProcessor(registry, def, "componentImportBeanFactoryPostProcessor"); 131 | } 132 | 133 | private BeanDefinitionHolder getServiceExportPostProcessor(BeanDefinitionRegistry registry, Object source) 134 | { 135 | RootBeanDefinition def = new RootBeanDefinition(ServiceExporterBeanPostProcessor.class); 136 | def.setSource(source); 137 | def.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR); 138 | 139 | return registerBeanPostProcessor(registry, def, "serviceExportBeanPostProcessor"); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/java/com/example/osgi/spring/scanner/extension/OSGIBundleScannerNamespaceHandler.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.scanner.extension; 2 | 3 | import org.springframework.beans.factory.xml.NamespaceHandlerSupport; 4 | 5 | /** 6 | * This class is simply the entry point for the spring xsd extension. 7 | * It maps the scan-indexes element in the xml to the proper parser 8 | */ 9 | public class OSGIBundleScannerNamespaceHandler extends NamespaceHandlerSupport 10 | { 11 | 12 | @Override 13 | public void init() 14 | { 15 | registerBeanDefinitionParser("scan-indexes",new OSGIBundleScannerBeanDefinitionParser()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/java/com/example/osgi/spring/scanner/extension/ServiceExporterBeanPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.scanner.extension; 2 | 3 | import java.util.Hashtable; 4 | 5 | import org.osgi.framework.BundleContext; 6 | import org.osgi.framework.ServiceRegistration; 7 | import org.springframework.beans.BeansException; 8 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 9 | import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; 10 | 11 | import com.example.osgi.spring.scanner.annotation.export.ExportAsService; 12 | 13 | /** 14 | * A BeanPostProcessor that exports OSGi services for beans annotated with both a *Component annotation and the ExportAsService annotation 15 | * This essentially does the same thing as the "public=true" on an atlassian plugin.xml component entry. 16 | *

17 | * This is implemented as a BeanPostProcessor because we need to service to come and go as the bean is created/destroyed 18 | */ 19 | public class ServiceExporterBeanPostProcessor implements DestructionAwareBeanPostProcessor 20 | { 21 | 22 | public static final String OSGI_SERVICE_SUFFIX = "_osgiService"; 23 | 24 | private final ExportedSeviceManager serviceManager; 25 | private final BundleContext bundleContext; 26 | private ConfigurableListableBeanFactory beanFactory; 27 | 28 | public ServiceExporterBeanPostProcessor(BundleContext bundleContext,ConfigurableListableBeanFactory beanFactory) 29 | { 30 | this.bundleContext = bundleContext; 31 | this.beanFactory = beanFactory; 32 | this.serviceManager = new ExportedSeviceManager(); 33 | } 34 | 35 | @Override 36 | public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException 37 | { 38 | 39 | if(isPublicComponent(bean)) 40 | { 41 | String serviceName = getServiceName(beanName); 42 | serviceManager.unregisterService(bundleContext, bean); 43 | 44 | if(beanFactory.containsBean(serviceName)) 45 | { 46 | Object serviceBean = beanFactory.getBean(serviceName); 47 | 48 | if(null != serviceBean) 49 | { 50 | beanFactory.destroyBean(serviceName,serviceBean); 51 | } 52 | } 53 | } 54 | } 55 | 56 | @Override 57 | public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException 58 | { 59 | return bean; 60 | } 61 | 62 | @Override 63 | public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException 64 | { 65 | Class[] interfaces; 66 | 67 | if (isPublicComponent(bean)) 68 | { 69 | String serviceName = getServiceName(beanName); 70 | interfaces = bean.getClass().getAnnotation(ExportAsService.class).value(); 71 | 72 | //if they didn't specify any interfaces, calculate them 73 | if (interfaces.length < 1) 74 | { 75 | interfaces = bean.getClass().getInterfaces(); 76 | 77 | //if we still don't have any, just export with the classname (yes, OSGi allows this. 78 | if (interfaces.length < 1) 79 | { 80 | interfaces = new Class[]{bean.getClass()}; 81 | } 82 | } 83 | 84 | try 85 | { 86 | ServiceRegistration reg = serviceManager.registerService(bundleContext, bean, beanName, new Hashtable(), interfaces); 87 | beanFactory.initializeBean(reg, serviceName); 88 | } 89 | catch (Exception e) 90 | { 91 | e.printStackTrace(); 92 | } 93 | } 94 | 95 | return bean; 96 | } 97 | 98 | private boolean isPublicComponent(Object bean) 99 | { 100 | return bean.getClass().isAnnotationPresent(ExportAsService.class); 101 | } 102 | 103 | private String getServiceName(String beanName) 104 | { 105 | return beanName + OSGI_SERVICE_SUFFIX; 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/java/com/example/osgi/spring/scanner/processor/ComponentAnnotationProcessor.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.scanner.processor; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.util.Set; 5 | 6 | import javax.annotation.processing.RoundEnvironment; 7 | import javax.annotation.processing.SupportedAnnotationTypes; 8 | import javax.lang.model.element.Element; 9 | import javax.lang.model.element.ElementKind; 10 | import javax.lang.model.element.Modifier; 11 | import javax.lang.model.element.TypeElement; 12 | import javax.lang.model.element.VariableElement; 13 | import javax.tools.Diagnostic; 14 | 15 | import com.example.osgi.spring.scanner.util.ClassIndexFiles; 16 | 17 | /** 18 | * Handles spring's @Component and our product specific *Component annotations and creates class index files for them. 19 | * Cross product components are listed in META-INF/plugin-components/components 20 | * Product specific comppnents are listed in META-INF/plugin-components/components-${productName} 21 | * 22 | * Entries in these files are either just the fully qualified class name of the component, or the fully qualified classname 23 | * plus the bean name defined as the value of the annotation separated by # 24 | * 25 | * Example: 26 | * 27 | * com.some.component.without.a.name.MyClass 28 | * com.some.component.with.a.name.MyClass#myBeanName 29 | */ 30 | @SupportedAnnotationTypes({"org.springframework.stereotype.Component", "javax.inject.Named", "com.atlassian.plugin.spring.scanner.annotation.component.*"}) 31 | public class ComponentAnnotationProcessor extends IndexWritingAnnotationProcessor 32 | { 33 | public static final String SPRING_COMPONENT_ANNOTATION = "org.springframework.stereotype.Component"; 34 | 35 | @Override 36 | public boolean process(Set annotations, RoundEnvironment roundEnv) 37 | { 38 | doProcess(annotations,roundEnv,ClassIndexFiles.COMPONENT_KEY); 39 | 40 | return false; 41 | } 42 | 43 | @Override 44 | public TypeAndAnnotation getTypeAndAnnotation(Element element, TypeElement anno) 45 | { 46 | TypeElement typeElement = null; 47 | Annotation annotation = null; 48 | TypeAndAnnotation typeAndAnnotation = null; 49 | Class componentAnnoClass; 50 | try 51 | { 52 | componentAnnoClass = Class.forName(anno.getQualifiedName().toString()); 53 | } 54 | catch (ClassNotFoundException e) 55 | { 56 | return typeAndAnnotation; 57 | } 58 | 59 | if (element instanceof TypeElement) 60 | { 61 | typeElement = (TypeElement) element; 62 | annotation = typeElement.getAnnotation(componentAnnoClass); 63 | 64 | typeAndAnnotation = new TypeAndAnnotation(typeElement, annotation); 65 | } 66 | else if (element instanceof VariableElement) 67 | { 68 | VariableElement variableElement = (VariableElement) element; 69 | if (ElementKind.PARAMETER.equals(variableElement.getKind()) && ElementKind.CONSTRUCTOR.equals(variableElement.getEnclosingElement().getKind())) 70 | { 71 | typeElement = (TypeElement) processingEnv.getTypeUtils().asElement(variableElement.asType()); 72 | annotation = variableElement.getAnnotation(componentAnnoClass); 73 | typeAndAnnotation = new TypeAndAnnotation(typeElement, annotation); 74 | } 75 | } 76 | 77 | if(null != typeElement && !ElementKind.CLASS.equals(typeElement.getKind())) 78 | { 79 | String message = "Annotation processor found a type [" + typeAndAnnotation.getTypeElement().getQualifiedName().toString() + "] annotated as a component, but the type is not a concrete class. NOT adding to index file!!"; 80 | processingEnv.getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING,message); 81 | 82 | typeAndAnnotation = null; 83 | } 84 | 85 | if(null != typeElement && typeElement.getModifiers().contains(Modifier.ABSTRACT)) 86 | { 87 | String message = "Annotation processor found a type [" + typeAndAnnotation.getTypeElement().getQualifiedName().toString() + "] annotated as a component, but the type is abstract. NOT adding to index file!!"; 88 | processingEnv.getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING,message); 89 | 90 | typeAndAnnotation = null; 91 | } 92 | 93 | return typeAndAnnotation; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/java/com/example/osgi/spring/scanner/processor/ComponentImportAnnotationProcessor.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.scanner.processor; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.util.Set; 5 | 6 | import javax.annotation.processing.RoundEnvironment; 7 | import javax.annotation.processing.SupportedAnnotationTypes; 8 | import javax.lang.model.element.Element; 9 | import javax.lang.model.element.ElementKind; 10 | import javax.lang.model.element.TypeElement; 11 | import javax.lang.model.element.VariableElement; 12 | 13 | import com.example.osgi.spring.scanner.util.ClassIndexFiles; 14 | 15 | /** 16 | * Handles *ComponentImport annotations and creates class index files for them. 17 | * Cross product imports are listed in META-INF/plugin-components/componentimport 18 | * Product specific imports are listed in META-INF/plugin-components/componentimport-${productName} 19 | * 20 | * Entries in these files are either just the fully qualified class name of the component, or the fully qualified classname 21 | * plus the bean name defined as the value of the annotation separated by # 22 | * 23 | * Example: 24 | * 25 | * com.some.component.without.a.name.MyClass 26 | * com.some.component.with.a.name.MyClass#myBeanName 27 | */ 28 | @SupportedAnnotationTypes("com.atlassian.plugin.spring.scanner.annotation.imports.*") 29 | public class ComponentImportAnnotationProcessor extends IndexWritingAnnotationProcessor 30 | { 31 | @Override 32 | public boolean process(Set annotations, RoundEnvironment roundEnv) 33 | { 34 | 35 | doProcess(annotations,roundEnv,ClassIndexFiles.COMPONENT_IMPORT_KEY); 36 | 37 | return false; 38 | } 39 | 40 | @Override 41 | public TypeAndAnnotation getTypeAndAnnotation(Element element, TypeElement anno) 42 | { 43 | TypeElement typeElement = null; 44 | Annotation annotation = null; 45 | TypeAndAnnotation typeAndAnnotation = null; 46 | Class componentAnnoClass; 47 | try 48 | { 49 | componentAnnoClass = Class.forName(anno.getQualifiedName().toString()); 50 | } 51 | catch (ClassNotFoundException e) 52 | { 53 | return typeAndAnnotation; 54 | } 55 | 56 | if (element instanceof VariableElement) 57 | { 58 | VariableElement variableElement = (VariableElement) element; 59 | if (ElementKind.PARAMETER.equals(variableElement.getKind()) && ElementKind.CONSTRUCTOR.equals(variableElement.getEnclosingElement().getKind())) 60 | { 61 | typeElement = (TypeElement) processingEnv.getTypeUtils().asElement(variableElement.asType()); 62 | annotation = variableElement.getAnnotation(componentAnnoClass); 63 | typeAndAnnotation = new TypeAndAnnotation(typeElement, annotation); 64 | } 65 | } 66 | 67 | return typeAndAnnotation; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/java/com/example/osgi/spring/scanner/processor/IndexWritingAnnotationProcessor.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.scanner.processor; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.FileNotFoundException; 5 | import java.io.IOException; 6 | import java.io.Reader; 7 | import java.io.Writer; 8 | import java.lang.annotation.Annotation; 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.lang.reflect.Method; 11 | import java.util.HashMap; 12 | import java.util.HashSet; 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | import javax.annotation.processing.AbstractProcessor; 17 | import javax.annotation.processing.Filer; 18 | import javax.annotation.processing.Processor; 19 | import javax.annotation.processing.RoundEnvironment; 20 | import javax.lang.model.SourceVersion; 21 | import javax.lang.model.element.Element; 22 | import javax.lang.model.element.TypeElement; 23 | import javax.tools.FileObject; 24 | import javax.tools.StandardLocation; 25 | 26 | import org.apache.commons.lang.StringUtils; 27 | 28 | import com.example.osgi.spring.scanner.util.ClassIndexFiles; 29 | 30 | /** 31 | * The base class for all processors that need to write class index files 32 | */ 33 | public abstract class IndexWritingAnnotationProcessor extends AbstractProcessor implements Processor 34 | { 35 | private Map> annotatedTypeMap = new HashMap>(); 36 | 37 | @Override 38 | public SourceVersion getSupportedSourceVersion() 39 | { 40 | return SourceVersion.latest(); 41 | 42 | } 43 | 44 | protected void doProcess(Set annotations, RoundEnvironment roundEnv, String indexKey) 45 | { 46 | try 47 | { 48 | for (TypeElement anno : annotations) 49 | { 50 | for (Element element : roundEnv.getElementsAnnotatedWith(anno)) 51 | { 52 | //we delegate to the subclasses for this as they may need special handling based on the kind of element 53 | TypeAndAnnotation typeAndAnnotation = getTypeAndAnnotation(element, anno); 54 | if (null == typeAndAnnotation || null == typeAndAnnotation.getTypeElement()) 55 | { 56 | continue; 57 | } 58 | 59 | //get the lower-case name of the product from the annotation 60 | String lowerFilterName = indexKey; 61 | 62 | //get the bean name specififed on the annotation (as it's value) if any 63 | String nameFromAnnotation = ""; 64 | try 65 | { 66 | Annotation componentAnno = typeAndAnnotation.getAnnotation(); 67 | Method valueMethod = componentAnno.getClass().getDeclaredMethod("value"); 68 | nameFromAnnotation = (String) valueMethod.invoke(componentAnno); 69 | } 70 | catch (NoSuchMethodException e) 71 | { 72 | //ignore 73 | } 74 | catch (InvocationTargetException e) 75 | { 76 | //ignore 77 | } 78 | catch (IllegalAccessException e) 79 | { 80 | //ignore 81 | } 82 | 83 | //Build the string to put in the index file. Always starts with fully qualified classname 84 | StringBuilder sb = new StringBuilder(typeAndAnnotation.getTypeElement().getQualifiedName().toString()); 85 | 86 | //if we have a custom bean name, tack it on 87 | if (StringUtils.isNotBlank(nameFromAnnotation)) 88 | { 89 | sb.append("#").append(nameFromAnnotation); 90 | } 91 | 92 | String typeName = sb.toString(); 93 | 94 | //get the proper list to add our type to based on product name (or cross-product) 95 | Set typeNames = getAnnotatedTypeNames(lowerFilterName); 96 | 97 | if (!typeNames.contains(typeName)) 98 | { 99 | typeNames.add(typeName); 100 | } 101 | } 102 | } 103 | 104 | if (!roundEnv.processingOver()) 105 | { 106 | return; 107 | } 108 | 109 | writeIndexFiles(indexKey); 110 | 111 | } 112 | catch (IOException e) 113 | { 114 | throw new RuntimeException(e); 115 | } 116 | } 117 | 118 | /** 119 | * Returns the fully qualified class name of the thing that's annotated as well as the instance of the annotation. 120 | * This delegates to subclasses as they may need to have special handling of this based on the kind of element we're dealing with. 121 | * @param element 122 | * @param anno 123 | * @return 124 | */ 125 | public abstract TypeAndAnnotation getTypeAndAnnotation(Element element, TypeElement anno); 126 | 127 | /** 128 | * Reads an existing index file (if any) and adds it's entries to the passed in set 129 | */ 130 | protected void readOldIndexFile(Set entries, String resourceName, Filer filer) throws IOException 131 | { 132 | BufferedReader reader = null; 133 | try 134 | { 135 | FileObject resource = filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceName); 136 | Reader resourceReader = resource.openReader(true); 137 | reader = new BufferedReader(resourceReader); 138 | 139 | String line = reader.readLine(); 140 | while (line != null) 141 | { 142 | entries.add(line); 143 | line = reader.readLine(); 144 | } 145 | } 146 | catch (FileNotFoundException e) 147 | { 148 | } 149 | catch (IOException e) 150 | { 151 | // Thrown by Eclipse JDT when not found 152 | } 153 | catch (UnsupportedOperationException e) 154 | { 155 | // Java6 does not support reading old index files 156 | } 157 | finally 158 | { 159 | if (reader != null) 160 | { 161 | reader.close(); 162 | } 163 | } 164 | } 165 | 166 | /** 167 | * returns the map of productName -> classNameSet 168 | * @return 169 | */ 170 | public Map> getAnnotatedTypeMap() 171 | { 172 | return annotatedTypeMap; 173 | } 174 | 175 | /** 176 | * gets a single set of classNames for the given product name 177 | * @param lowerFilterName 178 | * @return 179 | */ 180 | public Set getAnnotatedTypeNames(String lowerFilterName) 181 | { 182 | if (!getAnnotatedTypeMap().containsKey(lowerFilterName)) 183 | { 184 | getAnnotatedTypeMap().put(lowerFilterName, new HashSet()); 185 | } 186 | 187 | return getAnnotatedTypeMap().get(lowerFilterName); 188 | } 189 | 190 | /** 191 | * writes out the index files for the given annotion suffix (e.g. Component or ComponentImport) 192 | * handles writing out noth cross-product and product-specific files 193 | * @param file 194 | * @param suffix 195 | * @throws IOException 196 | */ 197 | protected void writeIndexFiles(String indexKey) throws IOException 198 | { 199 | for (Map.Entry> entry : getAnnotatedTypeMap().entrySet()) 200 | { 201 | StringBuilder filePath = new StringBuilder(ClassIndexFiles.INDEX_FILES_DIR ); 202 | filePath.append("/").append(indexKey); 203 | 204 | if (!entry.getKey().equals(indexKey)) 205 | { 206 | filePath.append("-").append(entry.getKey()); 207 | } 208 | 209 | writeIndexFile(entry.getValue(), filePath.toString(), processingEnv.getFiler()); 210 | } 211 | } 212 | 213 | /** 214 | * writes out a single index file 215 | * @param elementNameList 216 | * @param resourceName 217 | * @param filer 218 | * @throws IOException 219 | */ 220 | protected void writeIndexFile(Iterable elementNameList, String resourceName, Filer filer) 221 | throws IOException 222 | { 223 | Set entries = new HashSet(); 224 | for (String elementName : elementNameList) 225 | { 226 | entries.add(elementName); 227 | } 228 | 229 | readOldIndexFile(entries, resourceName, filer); 230 | 231 | FileObject file = filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceName); 232 | Writer writer = file.openWriter(); 233 | for (String entry : entries) 234 | { 235 | writer.write(entry); 236 | writer.write("\n"); 237 | } 238 | writer.close(); 239 | } 240 | 241 | /** 242 | * used to return the class type and annotation 243 | */ 244 | protected class TypeAndAnnotation 245 | { 246 | private final TypeElement typeElement; 247 | private final Annotation annotation; 248 | 249 | protected TypeAndAnnotation(TypeElement typeElement, Annotation annotation) 250 | { 251 | this.typeElement = typeElement; 252 | this.annotation = annotation; 253 | } 254 | 255 | public TypeElement getTypeElement() 256 | { 257 | return typeElement; 258 | } 259 | 260 | public Annotation getAnnotation() 261 | { 262 | return annotation; 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/java/com/example/osgi/spring/scanner/util/AnnotationIndexReader.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.scanner.util; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.FileNotFoundException; 5 | import java.io.IOException; 6 | import java.io.InputStreamReader; 7 | import java.net.URL; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import org.osgi.framework.Bundle; 12 | 13 | import com.google.common.base.Charsets; 14 | 15 | /** 16 | * A utility class to read index files from a classloader or bundle. 17 | * Can handle reading a single index file, or concating multiple index files based on the 18 | * currently running product. 19 | * 20 | */ 21 | public class AnnotationIndexReader 22 | { 23 | /** 24 | * reads a single file from a bundle 25 | * @param resourceFile 26 | * @param bundle 27 | * @return 28 | */ 29 | public static List readIndexFile(String resourceFile, Bundle bundle) 30 | { 31 | URL url = bundle.getResource(resourceFile); 32 | return readIndexFile(url); 33 | } 34 | 35 | /** 36 | * reads a single file from a classloader 37 | * @param resourceFile 38 | * @param classLoader 39 | * @return 40 | */ 41 | public static List readIndexFile(String resourceFile, ClassLoader classLoader) 42 | { 43 | URL url = classLoader.getResource(resourceFile); 44 | return readIndexFile(url); 45 | } 46 | 47 | /** 48 | * reads the cross-product file as well as the product-specific file for the currently running product 49 | * and returns their contents as a single list 50 | * 51 | * @param resourceFile 52 | * @param bundle 53 | * @return 54 | */ 55 | public static List readAllIndexFilesForProduct(String resourceFile, Bundle bundle) 56 | { 57 | List entries = new ArrayList(); 58 | 59 | URL url = bundle.getResource(resourceFile); 60 | 61 | entries.addAll(readIndexFile(url)); 62 | 63 | return entries; 64 | } 65 | 66 | /** 67 | * reads the cross-product file as well as the product-specific file for the currently running product 68 | * and returns their contents as a single list 69 | * 70 | * @param resourceFile 71 | * @param classLoader 72 | * @return 73 | */ 74 | public static List readAllIndexFilesForProduct(String resourceFile, ClassLoader classLoader) 75 | { 76 | List entries = new ArrayList(); 77 | 78 | URL url = classLoader.getResource(resourceFile); 79 | 80 | entries.addAll(readIndexFile(url)); 81 | 82 | return entries; 83 | } 84 | 85 | public static List readIndexFile(URL url) 86 | { 87 | List resources = new ArrayList(); 88 | 89 | try 90 | { 91 | if (null == url) 92 | { 93 | return resources; 94 | } 95 | 96 | BufferedReader reader; 97 | try 98 | { 99 | reader = new BufferedReader(new InputStreamReader(url.openStream(), Charsets.UTF_8)); 100 | } 101 | catch (FileNotFoundException e) 102 | { 103 | return resources; 104 | } 105 | 106 | String line = reader.readLine(); 107 | while (line != null) 108 | { 109 | resources.add(line); 110 | 111 | line = reader.readLine(); 112 | } 113 | 114 | reader.close(); 115 | } 116 | catch (IOException e) 117 | { 118 | throw new RuntimeException("Cannot read index file [" + url.toString() + "]", e); 119 | } 120 | 121 | return resources; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/java/com/example/osgi/spring/scanner/util/ClassIndexFiles.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.scanner.util; 2 | 3 | public class ClassIndexFiles 4 | { 5 | public static final String INDEX_FILES_DIR = "META-INF/plugin-components"; 6 | 7 | public static final String COMPONENT_KEY = "component"; 8 | public static final String COMPONENT_INDEX_FILE = INDEX_FILES_DIR + "/" + COMPONENT_KEY; 9 | 10 | public static final String COMPONENT_IMPORT_KEY = "imports"; 11 | public static final String COMPONENT_IMPORT_INDEX_FILE = INDEX_FILES_DIR + "/" + COMPONENT_IMPORT_KEY; 12 | } 13 | -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/resources/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | com.example.osgi.spring.scanner.processor.ComponentAnnotationProcessor 2 | com.example.osgi.spring.scanner.processor.ComponentImportAnnotationProcessor -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/resources/META-INF/spring.handlers: -------------------------------------------------------------------------------- 1 | http\://www.example.com/schema/osgi-bundle-scanner=com.example.osgi.spring.scanner.extension.OSGIBundleScannerNamespaceHandler -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/resources/META-INF/spring.schemas: -------------------------------------------------------------------------------- 1 | http\://www.example.com/schema/osgi-bundle-scanner/osgi-bundle-scanner.xsd=com/example/osgi/spring/scanner/osgi-bundle-scanner.xsd -------------------------------------------------------------------------------- /osgi-spring-bundle-scanner/src/main/resources/com/example/osgi/spring/scanner/osgi-bundle-scanner.xsd: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /osgi-spring-extender/bnd.bnd: -------------------------------------------------------------------------------- 1 | -dsannotations: 2 | Bundle-ManifestVersion: 2 3 | Export-Package: com.example.osgi.spring.extender.external 4 | Bundle-Version: 0.0.1 5 | Private-Package: \ 6 | com.example.osgi.spring.extender,\ 7 | org.eclipse.gemini.blueprint.context.support 8 | Fragment-Host: org.eclipse.gemini.blueprint.extender 9 | Import-Package: \ 10 | com.example.osgi.spring.scanner.*,\ 11 | org.slf4j.*,\ 12 | * 13 | Bundle-Name: osgi-bundle-extender 14 | Bundle-Description: Extends Eclipse Gemini Blueprint to use custom Spring scanner which scans \ 15 | OSGI bundles -------------------------------------------------------------------------------- /osgi-spring-extender/osgi-spring-extender.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath 'biz.aQute.bnd:biz.aQute.bnd.gradle:4.0.0' 7 | } 8 | } 9 | 10 | plugins { 11 | id 'java' apply true 12 | id 'eclipse' apply true 13 | } 14 | 15 | apply plugin: 'biz.aQute.bnd.builder' 16 | 17 | repositories { 18 | mavenCentral() 19 | maven { url "https://maven.atlassian.com/content/repositories/atlassian-public/" } 20 | maven { url "https://repo.spring.io/libs-release" } 21 | maven { url "https://repo.spring.io/plugins-release" } 22 | } 23 | 24 | sourceCompatibility = 1.8 25 | group = 'com.example.plugins.osgi.spring.extender' 26 | version = '0.0.1' 27 | 28 | eclipse.project { 29 | natures 'org.springsource.ide.eclipse.gradle.core.nature' 30 | } 31 | 32 | dependencies { 33 | 34 | compileOnly "org.eclipse.gemini.blueprint:gemini-blueprint-extender:3.0.0.M01" 35 | compileOnly "org.apache.felix:org.apache.felix.framework:6.0.0" 36 | compileOnly "org.apache.felix:org.osgi.core:1.4.0" 37 | compile "org.slf4j:slf4j-simple:1.7.25" 38 | compileOnly "org.springframework:spring-webmvc:5.0.7.RELEASE" 39 | 40 | } 41 | -------------------------------------------------------------------------------- /osgi-spring-extender/src/main/java/com/example/osgi/spring/extender/NonValidatingOsgiApplicationContextCreator.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.extender; 2 | 3 | import java.util.List; 4 | 5 | import org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext; 6 | import org.eclipse.gemini.blueprint.context.support.NonValidatingOsgiBundleXmlApplicationContext; 7 | import org.eclipse.gemini.blueprint.extender.OsgiApplicationContextCreator; 8 | import org.eclipse.gemini.blueprint.extender.support.ApplicationContextConfiguration; 9 | import org.eclipse.gemini.blueprint.extender.support.DefaultOsgiApplicationContextCreator; 10 | import org.eclipse.gemini.blueprint.extender.support.scanning.ConfigurationScanner; 11 | import org.eclipse.gemini.blueprint.extender.support.scanning.DefaultConfigurationScanner; 12 | import org.eclipse.gemini.blueprint.util.OsgiStringUtils; 13 | import org.osgi.framework.Bundle; 14 | import org.osgi.framework.BundleContext; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | import org.springframework.util.ObjectUtils; 18 | 19 | import com.example.osgi.spring.extender.external.ApplicationContextPreProcessor; 20 | 21 | /** 22 | * Application context creator that will use a special application context that disables XML Schema validation 23 | * 24 | * @since 2.5.0 25 | */ 26 | public class NonValidatingOsgiApplicationContextCreator implements OsgiApplicationContextCreator { 27 | private static final Logger log = LoggerFactory.getLogger(DefaultOsgiApplicationContextCreator.class); 28 | private final List applicationContextPreProcessors; 29 | 30 | private ConfigurationScanner configurationScanner = new DefaultConfigurationScanner(); 31 | 32 | public NonValidatingOsgiApplicationContextCreator(List applicationContextPreProcessors) { 33 | this.applicationContextPreProcessors = applicationContextPreProcessors; 34 | } 35 | 36 | /** 37 | * Creates an application context that disables validation. Most of this code is copy/pasted from 38 | * {@link DefaultOsgiApplicationContextCreator} 39 | * 40 | * @param bundleContext The bundle context for the application context 41 | * @return The new application context 42 | * @throws Exception If anything goes wrong 43 | */ 44 | public DelegatedExecutionOsgiBundleApplicationContext createApplicationContext(BundleContext bundleContext) throws Exception { 45 | Bundle bundle = bundleContext.getBundle(); 46 | ApplicationContextConfiguration config = new ApplicationContextConfiguration(bundle, configurationScanner); 47 | if (log.isTraceEnabled()) 48 | log.trace("Created configuration " + config + " for bundle " 49 | + OsgiStringUtils.nullSafeNameAndSymName(bundle)); 50 | 51 | // it's not a spring bundle, ignore it 52 | if (!isSpringPoweredBundle(bundle, config)) { 53 | return null; 54 | } 55 | 56 | log.info("Discovered configurations " + ObjectUtils.nullSafeToString(config.getConfigurationLocations()) 57 | + " in bundle [" + OsgiStringUtils.nullSafeNameAndSymName(bundle) + "]"); 58 | 59 | // This is the one new line, which uses our application context and not the other one 60 | DelegatedExecutionOsgiBundleApplicationContext sdoac = new NonValidatingOsgiBundleXmlApplicationContext( 61 | config.getConfigurationLocations()); 62 | 63 | sdoac.setBundleContext(bundleContext); 64 | sdoac.setPublishContextAsService(config.isPublishContextAsService()); 65 | 66 | for (ApplicationContextPreProcessor processor : applicationContextPreProcessors) { 67 | processor.process(bundle, sdoac); 68 | } 69 | 70 | return sdoac; 71 | } 72 | 73 | boolean isSpringPoweredBundle(Bundle bundle, ApplicationContextConfiguration config) { 74 | // Check for the normal configuration xml files 75 | if (config.isSpringPoweredBundle()) { 76 | return true; 77 | } 78 | 79 | // Check any preprocessors, as they may solely use annotations 80 | else { 81 | for (ApplicationContextPreProcessor processor : applicationContextPreProcessors) { 82 | if (processor.isSpringPoweredBundle(bundle)) { 83 | return true; 84 | } 85 | } 86 | } 87 | 88 | // Return false as the default 89 | return false; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /osgi-spring-extender/src/main/java/com/example/osgi/spring/extender/ThreadPoolAsyncTaskExecutor.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.extender; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.core.task.AsyncTaskExecutor; 6 | 7 | import java.util.concurrent.Callable; 8 | import java.util.concurrent.ExecutorService; 9 | import java.util.concurrent.Executors; 10 | import java.util.concurrent.Future; 11 | import java.util.concurrent.ThreadFactory; 12 | import java.util.concurrent.TimeUnit; 13 | import java.util.concurrent.atomic.AtomicInteger; 14 | 15 | /** 16 | * Executes spring tasks using a cached thread pool that expands as necessary. Overrides the default Spring executor 17 | * that spawns a new thread for every application context creation. 18 | * 19 | * @since 2.5.0 20 | */ 21 | public class ThreadPoolAsyncTaskExecutor implements AsyncTaskExecutor { 22 | private static final Logger log = LoggerFactory.getLogger(ThreadPoolAsyncTaskExecutor.class); 23 | 24 | private final ExecutorService executor = Executors.newCachedThreadPool(new NamedThreadFactory()); 25 | 26 | /** 27 | * Executes the runnable 28 | * 29 | * @param task The runnable task 30 | * @param startTimeout The start timeout (ignored) 31 | */ 32 | @Override 33 | public void execute(Runnable task, long startTimeout) { 34 | // yes, we ignore the start timeout 35 | executor.execute(task); 36 | } 37 | 38 | @Override 39 | public Future submit(Runnable task) { 40 | return executor.submit(task); 41 | } 42 | 43 | @Override 44 | public Future submit(Callable task) { 45 | return executor.submit(task); 46 | } 47 | 48 | /** 49 | * Executes the runnable 50 | * 51 | * @param task The runnable task 52 | */ 53 | @Override 54 | public void execute(Runnable task) { 55 | this.execute(task, -1); 56 | } 57 | 58 | /** 59 | * Shuts down the internal {@code ExecutorService} to ensure that all threads are stopped in order to allow the JVM 60 | * to terminate cleanly in a timely fashion. 61 | */ 62 | public void shutdown() { 63 | log.debug("Attempting to shutdown ExecutorService"); 64 | 65 | executor.shutdown(); 66 | try { 67 | if (executor.awaitTermination(5, TimeUnit.SECONDS)) { 68 | log.debug("ExecutorService has shutdown gracefully"); 69 | } else { 70 | //The executor did not shutdown within the timeout. We can't wait forever, though, so issue a 71 | //shutdownNow() and give it another 5 seconds 72 | log.warn("ExecutorService did not shutdown within the timeout; forcing shutdown"); 73 | 74 | executor.shutdownNow(); 75 | if (executor.awaitTermination(5, TimeUnit.SECONDS)) { 76 | //The forced shutdown has brought the executor down. Not ideal, but acceptable 77 | log.debug("ExecutorService has been forced to shutdown"); 78 | } else { 79 | //We can't delay execution indefinitely waiting, so log a warning. The JVM may not shut down 80 | //if this service does not stop (because it uses non-daemon threads), so this may be helpful 81 | //in debugging should that happen. 82 | log.warn("ExecutorService did not shutdown; it will be abandoned"); 83 | } 84 | } 85 | } catch (InterruptedException e) { 86 | log.warn("Interrupted while waiting for the executor service to shutdown; some worker threads may " + 87 | "still be running"); 88 | Thread.currentThread().interrupt(); 89 | } 90 | } 91 | 92 | /** 93 | * Thread factory that names the threads for the executor 94 | */ 95 | private static class NamedThreadFactory implements ThreadFactory { 96 | private final AtomicInteger counter = new AtomicInteger(); 97 | 98 | @Override 99 | public Thread newThread(Runnable r) { 100 | Thread thread = new Thread(r); 101 | thread.setDaemon(false); 102 | thread.setName("ThreadPoolAsyncTaskExecutor::Thread " + counter.incrementAndGet()); 103 | return thread; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /osgi-spring-extender/src/main/java/com/example/osgi/spring/extender/external/ApplicationContextPreProcessor.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.spring.extender.external; 2 | 3 | import org.osgi.framework.Bundle; 4 | import org.springframework.context.ConfigurableApplicationContext; 5 | 6 | /** 7 | * Hook to customize the spring application context before it is populated 8 | * 9 | * @since 2.6 10 | */ 11 | public interface ApplicationContextPreProcessor { 12 | /** 13 | * Detects if this bundle is a Spring bundle. This allows a pre-processor to have a Spring context built for a bundle 14 | * even if the bundle doesn't have the Spring context header or any XML files in META-INF/spring. 15 | * 16 | * @param bundle The bundle to create an application context for 17 | * @return True if a spring context should be created, false otherwise 18 | */ 19 | boolean isSpringPoweredBundle(Bundle bundle); 20 | 21 | /** 22 | * Process a context before it is populated, usually via adding 23 | * {@link org.springframework.beans.factory.config.BeanFactoryPostProcessor} instances. 24 | * 25 | * @param bundle The target bundle 26 | * @param applicationContext The target application context before population 27 | */ 28 | void process(Bundle bundle, ConfigurableApplicationContext applicationContext); 29 | } 30 | -------------------------------------------------------------------------------- /osgi-spring-extender/src/main/java/org/eclipse/gemini/blueprint/context/support/NonValidatingOsgiBundleXmlApplicationContext.java: -------------------------------------------------------------------------------- 1 | package org.eclipse.gemini.blueprint.context.support; 2 | 3 | import org.eclipse.gemini.blueprint.context.BundleContextAware; 4 | import org.eclipse.gemini.blueprint.context.support.OsgiBundleXmlApplicationContext; 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver; 7 | import org.springframework.beans.factory.config.BeanPostProcessor; 8 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 9 | import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; 10 | import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; 11 | 12 | /** 13 | * Application context that initializes the bean definition reader to not validate via XML Schema. Note that by 14 | * turning this off, certain defaults won't be populated like expected. For example, XML Schema provides the default 15 | * autowire value of "default", but without this validation, that value is not set so autowiring will be turned off. 16 | * 17 | * This class exists in the same package as the parent so the log messages won't get confused as the parent class 18 | * logs against the instance class. 19 | */ 20 | public class NonValidatingOsgiBundleXmlApplicationContext extends OsgiBundleXmlApplicationContext { 21 | 22 | public NonValidatingOsgiBundleXmlApplicationContext(final String[] configLocations) { 23 | super(configLocations); 24 | } 25 | 26 | @Override 27 | protected void initBeanDefinitionReader(final XmlBeanDefinitionReader beanDefinitionReader) { 28 | super.initBeanDefinitionReader(beanDefinitionReader); 29 | 30 | // Don't validate XML. 31 | beanDefinitionReader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_NONE); 32 | // Because not validating this is required 33 | beanDefinitionReader.setNamespaceAware(true); 34 | } 35 | 36 | @Override 37 | protected void customizeBeanFactory(final DefaultListableBeanFactory beanFactory) { 38 | super.customizeBeanFactory(beanFactory); 39 | 40 | 41 | // TODO: Maybe take this back out?!?!? 42 | beanFactory.createBean(RequestMappingHandlerMapping.class); 43 | beanFactory.addBeanPostProcessor(new BundleContextAwareBeanPostProcessor()); 44 | beanFactory.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); 45 | } 46 | 47 | /** 48 | * If bean is BundleContextAware then we need to inject the bundle context here. 49 | */ 50 | private class BundleContextAwareBeanPostProcessor implements BeanPostProcessor { 51 | @Override 52 | public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException { 53 | // Inject the BundleContext into beans which mark themselves as needing it. 54 | if (bean instanceof BundleContextAware) { 55 | ((BundleContextAware) bean).setBundleContext(getBundleContext()); 56 | } 57 | return bean; 58 | } 59 | 60 | @Override 61 | public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException { 62 | // Nothing to do after initialization 63 | return bean; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /osgi-spring-extender/src/main/resources/META-INF/spring/extender/spring-event-bridge.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /osgi-spring-wired-test-bundle/bnd.bnd: -------------------------------------------------------------------------------- 1 | -dsannotations: 2 | Bundle-ManifestVersion: 2 3 | Export-Package: com.example.osgi.plugins 4 | Bundle-Version: 0.0.1 5 | Bundle-Name: spring-wired-plugin-test-bundle 6 | Bundle-SymbolicName: spring-wired-plugin-test-bundle 7 | Import-Package: \ 8 | com.example.osgi.spring.scanner.*,\ 9 | com.example.osgi.spring.scanner.annotation.export.*,\ 10 | com.example.osgi.spring.scanner.annotation.import.*,\ 11 | com.example.osgi.spring.scanner.extention.*,\ 12 | com.example.osgi.spring.scanner.processor.*,\ 13 | com.example.osgi.spring.scanner.util.*,\ 14 | org.springframework.beans.factory.xml.*,\ 15 | * -------------------------------------------------------------------------------- /osgi-spring-wired-test-bundle/osgi-spring-wired-test-bundle.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath 'biz.aQute.bnd:biz.aQute.bnd.gradle:4.0.0' 7 | } 8 | } 9 | 10 | plugins { 11 | id 'java' apply true 12 | id 'eclipse' apply true 13 | } 14 | 15 | apply plugin: 'biz.aQute.bnd.builder' 16 | 17 | repositories { 18 | mavenCentral() 19 | maven { url "https://maven.atlassian.com/content/repositories/atlassian-public/" } 20 | maven { url "https://repo.spring.io/libs-release" } 21 | maven { url "https://repo.spring.io/plugins-release" } 22 | } 23 | 24 | sourceCompatibility = 1.8 25 | group = 'com.example.osgi.plugins' 26 | version = '0.0.1' 27 | 28 | eclipse.project { 29 | natures 'org.springsource.ide.eclipse.gradle.core.nature' 30 | } 31 | 32 | configurations { 33 | extraLibs 34 | } 35 | 36 | dependencies { 37 | 38 | compileOnly project(":osgi-spring-bundle-scanner") 39 | //extraLibs project(":osgi-spring-bundle-scanner") 40 | 41 | //compile "org.eclipse.gemini.blueprint:gemini-blueprint-core:3.0.0.M01" 42 | //compile "org.eclipse.gemini.blueprint:gemini-blueprint-io:3.0.0.M01" 43 | compileOnly "org.eclipse.gemini.blueprint:gemini-blueprint-extender:3.0.0.M01" 44 | compileOnly "org.springframework:spring-webmvc:5.0.7.RELEASE" 45 | 46 | //compile "org.apache.servicemix.bundles:org.apache.servicemix.bundles.spring-aop:5.0.7.RELEASE_1" 47 | //compile "org.apache.servicemix.bundles:org.apache.servicemix.bundles.spring-web:5.0.7.RELEASE_1" 48 | //compile "org.apache.servicemix.bundles:org.apache.servicemix.bundles.spring-webmvc:5.0.7.RELEASE_1" 49 | //compile "org.apache.servicemix.bundles:org.apache.servicemix.bundles.spring-beans:5.0.7.RELEASE_1" 50 | //compile "org.apache.servicemix.bundles:org.apache.servicemix.bundles.spring-context:5.0.7.RELEASE_1" 51 | //compile "org.apache.servicemix.bundles:org.apache.servicemix.bundles.spring-core:5.0.7.RELEASE_1" 52 | //compile "org.apache.servicemix.bundles:org.apache.servicemix.bundles.spring-expression:5.0.7.RELEASE_1" 53 | //compile "org.eclipse.gemini.blueprint:gemini-blueprint-extensions:3.0.0.M01" 54 | 55 | //compile "org.apache.servicemix.bundles:org.apache.servicemix.bundles.aopalliance:1.0_6" 56 | //compile "commons-logging:commons-logging:1.2" 57 | 58 | compile "org.osgi:org.osgi.framework:1.9.0" 59 | 60 | } 61 | 62 | //jar { 63 | // from { 64 | // configurations.extraLibs.collect { it.isDirectory() ? it : zipTree(it) } 65 | // } 66 | //} 67 | -------------------------------------------------------------------------------- /osgi-spring-wired-test-bundle/src/main/resources/META-INF/spring/spring-scanner.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /osgi-spring-wired-test-bundle/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Yeah Baby!! Spring Web in OSGI Bundle!!

5 |

Totes McGroats!!

6 | 7 | 8 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include "osgi-spring-bundle-scanner" 2 | include "osgi-spring-extender" 3 | include "osgi-spring-wired-test-bundle" 4 | 5 | rootProject.name = 'example-spring-boot-embedded-felix' 6 | rootProject.children.each {project -> 7 | project.buildFileName = "${project.name}.gradle" 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/example/osgi/framework/App.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.framework; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.PropertySource; 6 | import org.springframework.context.annotation.PropertySources; 7 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 8 | 9 | @SpringBootApplication 10 | @PropertySources({ 11 | @PropertySource("classpath:application-${spring.profiles.active}.properties") 12 | }) 13 | public class App{ 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(App.class, args); 17 | } 18 | 19 | // Note: Need to specify "Bundle-ManifestVersion: 2" in any bundle used in this framework 20 | 21 | } -------------------------------------------------------------------------------- /src/main/java/com/example/osgi/framework/felix/FelixService.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.framework.felix; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.InputStreamReader; 5 | import java.util.HashMap; 6 | 7 | import org.apache.felix.framework.FrameworkFactory; 8 | import org.apache.felix.main.AutoProcessor; 9 | import org.osgi.framework.launch.Framework; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.context.event.ApplicationReadyEvent; 14 | import org.springframework.context.event.EventListener; 15 | import org.springframework.stereotype.Service; 16 | 17 | import com.example.osgi.framework.util.SpringPropertiesHelper; 18 | 19 | @Service 20 | public class FelixService { 21 | 22 | private static final Logger logger = LoggerFactory.getLogger(FelixService.class); 23 | private static final String SHUTDOWN_HOOK_PROP = "felix.shutdown.hook"; 24 | 25 | private Framework framework = null; 26 | 27 | @Autowired 28 | private SpringAwareFelixBundleListener springAwareFelixBundleListener; 29 | 30 | @Autowired 31 | private SpringPropertiesHelper propertyHelper; 32 | 33 | public FelixService() { 34 | // Start the Felix framework once all the beans have been created 35 | // So that we get our property values injected and available 36 | logger.debug("***** FelixService Component Constructed *****"); 37 | } 38 | 39 | @EventListener(ApplicationReadyEvent.class) 40 | void startFramework() { 41 | 42 | // Get Felix properties from Spring Boot config 43 | HashMap frameworkProps = propertyHelper.getProps(); 44 | 45 | 46 | logger.info("----------------------------------------------------------"); 47 | logger.info(frameworkProps.toString()); 48 | logger.info("| Felix : Framework STARTING"); 49 | logger.info("| Felix : Loading bundles from ("+frameworkProps.get("felix.auto.deploy.dir").toString()+")"); 50 | 51 | addShutdownHook(framework,frameworkProps); 52 | 53 | try 54 | { 55 | // Create an instance and initialise the framework. 56 | FrameworkFactory factory = getFrameworkFactory(); 57 | framework = factory.newFramework(frameworkProps); 58 | framework.init(); 59 | 60 | // Use the system bundle context to process the auto-deploy 61 | // and auto-install/auto-start properties. 62 | AutoProcessor.process(frameworkProps, framework.getBundleContext()); 63 | 64 | // Log Bundle Activations 65 | framework.getBundleContext().addBundleListener(springAwareFelixBundleListener); 66 | 67 | // Start the framework. 68 | framework.start(); 69 | 70 | // Wait for framework to stop -- then exit the VM. 71 | logger.info("| Felix : Framework STARTED - listening for shutdown hook"); 72 | logger.info("----------------------------------------------------------"); 73 | framework.waitForStop(0); 74 | System.exit(0); 75 | } 76 | catch (Exception ex) 77 | { 78 | logger.error("| Felix : Framework FAILED TO START - Errors: " + ex); 79 | logger.info("----------------------------------------------------------"); 80 | ex.printStackTrace(); 81 | System.exit(0); 82 | } 83 | } 84 | 85 | private void addShutdownHook(Framework framework, HashMap frameworkProps) { 86 | 87 | // Add a shutdown hook to clean stop the framework. 88 | String enableHook = frameworkProps.get(SHUTDOWN_HOOK_PROP); 89 | if ((enableHook == null) || !enableHook.equalsIgnoreCase("false")) 90 | { 91 | Runtime.getRuntime().addShutdownHook(new Thread("Felix Shutdown Hook") { 92 | public void run() 93 | { 94 | logger.info(""); 95 | logger.info("| Felix : Framework STOPPING - shutdown hook detected"); 96 | logger.info(""); 97 | try 98 | { 99 | if (framework != null) 100 | { 101 | framework.stop(); 102 | framework.waitForStop(0); 103 | logger.info("| Felix : Framework STOPPED"); 104 | } 105 | } 106 | catch (Exception ex) 107 | { 108 | logger.error("| Felix : Error stopping framework - Errors: " + ex); 109 | } 110 | } 111 | }); 112 | } 113 | 114 | } 115 | 116 | private FrameworkFactory getFrameworkFactory() throws Exception 117 | { 118 | java.net.URL url = FelixService.class.getClassLoader().getResource( 119 | "META-INF/services/org.osgi.framework.launch.FrameworkFactory" 120 | ); 121 | if (url != null) 122 | { 123 | BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream())); 124 | try 125 | { 126 | for (String s = br.readLine(); s != null; s = br.readLine()) 127 | { 128 | s = s.trim(); 129 | // Try to load first non-empty, non-commented line. 130 | if ((s.length() > 0) && (s.charAt(0) != '#')) 131 | { 132 | return (FrameworkFactory) Class.forName(s).newInstance(); 133 | } 134 | } 135 | } 136 | finally 137 | { 138 | if (br != null) br.close(); 139 | } 140 | } 141 | 142 | throw new Exception("Could not find framework factory."); 143 | } 144 | 145 | } -------------------------------------------------------------------------------- /src/main/java/com/example/osgi/framework/felix/SpringAwareFelixBundleListener.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.framework.felix; 2 | 3 | import org.osgi.framework.Bundle; 4 | import org.osgi.framework.BundleEvent; 5 | import org.osgi.framework.BundleListener; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.context.event.ApplicationStartedEvent; 10 | import org.springframework.context.event.EventListener; 11 | import org.springframework.stereotype.Component; 12 | import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; 13 | 14 | @Component 15 | public class SpringAwareFelixBundleListener implements BundleListener { 16 | 17 | private static final Logger logger = LoggerFactory.getLogger(SpringAwareFelixBundleListener.class); 18 | 19 | @Autowired 20 | RequestMappingHandlerMapping requestMapping; 21 | 22 | public SpringAwareFelixBundleListener() { 23 | } 24 | 25 | @EventListener(ApplicationStartedEvent.class) 26 | public void init() { 27 | 28 | } 29 | 30 | @Override 31 | public void bundleChanged(BundleEvent bundleEvent) { 32 | 33 | logger.info(String.format( 34 | "Bundle %s changed state to %s", 35 | bundleEvent.getBundle().getSymbolicName(), 36 | getBundleStateAsString(bundleEvent.getBundle().getState() 37 | ))); 38 | 39 | } 40 | 41 | private String getBundleStateAsString(int state) { 42 | 43 | switch(state) { 44 | case Bundle.ACTIVE: return "Active"; 45 | case Bundle.INSTALLED: return "Installed"; 46 | case Bundle.RESOLVED: return "Resolved"; 47 | case Bundle.STARTING: return "Starting"; 48 | case Bundle.STOPPING: return "Stopping"; 49 | case Bundle.UNINSTALLED: return "Uninstalled"; 50 | default: return "Unknown"; 51 | } 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/example/osgi/framework/util/SpringPropertiesHelper.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.framework.util; 2 | 3 | import java.util.HashMap; 4 | 5 | import javax.annotation.PostConstruct; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.env.OriginTrackedMapPropertySource; 11 | import org.springframework.context.ApplicationContext; 12 | import org.springframework.core.env.ConfigurableEnvironment; 13 | import org.springframework.core.env.EnumerablePropertySource; 14 | import org.springframework.core.env.Environment; 15 | import org.springframework.core.env.MutablePropertySources; 16 | import org.springframework.core.env.PropertySource; 17 | import org.springframework.stereotype.Component; 18 | 19 | @Component 20 | public class SpringPropertiesHelper { 21 | 22 | private static final Logger logger = LoggerFactory.getLogger(SpringPropertiesHelper.class); 23 | private HashMap props; 24 | private boolean loaded = false; 25 | 26 | @Autowired 27 | ApplicationContext ctx; 28 | 29 | public SpringPropertiesHelper(){ 30 | this.props = new HashMap(); 31 | } 32 | 33 | public HashMap getProps(){ 34 | if(!loaded) { 35 | loadProperties(); 36 | } 37 | return this.props; 38 | } 39 | 40 | @PostConstruct 41 | private void loadProperties() { 42 | Environment env = ctx.getEnvironment(); 43 | if (ctx.getEnvironment() instanceof ConfigurableEnvironment) { 44 | // Get all Spring detected Property sources 45 | MutablePropertySources sources = ((ConfigurableEnvironment) env).getPropertySources(); 46 | MutablePropertySources filteredSources = new MutablePropertySources(); 47 | sources.iterator().forEachRemaining(s -> { 48 | 49 | // If its from a classpath .properties file 50 | if(s.getClass().isAssignableFrom(OriginTrackedMapPropertySource.class)) { 51 | // only load props if source matches active profile 52 | if(s.getName().toString().contains("application") && env.acceptsProfiles("prod")) { 53 | filteredSources.addLast(s); 54 | }else if(s.getName().toString().contains("application-dev") && env.acceptsProfiles("dev")) { 55 | filteredSources.addLast(s); 56 | } 57 | }else { 58 | filteredSources.addLast(s); 59 | } 60 | 61 | logger.debug("Loaded property: " + s.getName().toString()); 62 | 63 | }); 64 | for (PropertySource propertySource : filteredSources ) { 65 | if (propertySource instanceof EnumerablePropertySource) { 66 | for (String key : ((EnumerablePropertySource) propertySource).getPropertyNames()) { 67 | props.put(key, (String)propertySource.getProperty(key)); 68 | } 69 | } 70 | } 71 | } 72 | loaded = true; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/example/osgi/framework/web/DispatcherSetup.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.framework.web; 2 | 3 | import javax.servlet.ServletContext; 4 | import javax.servlet.ServletException; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; 9 | import org.springframework.boot.web.servlet.ServletContextInitializer; 10 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 11 | import org.springframework.context.ApplicationContext; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.web.servlet.DispatcherServlet; 15 | 16 | @Configuration 17 | public class DispatcherSetup extends DispatcherServletAutoConfiguration { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(DispatcherSetup.class); 20 | 21 | @Bean 22 | public DispatcherServlet dispatcherServlet() { 23 | 24 | return new DispatcherServlet() { 25 | 26 | @Override 27 | protected void onRefresh(ApplicationContext context) { 28 | // TODO Auto-generated method stub 29 | super.onRefresh(context); 30 | DispatcherSetup.logger.info("SERVLET CONTEXT REFRESHED AS " + context.toString()); 31 | } 32 | 33 | }; 34 | } 35 | 36 | @Bean 37 | public ServletContextInitializer contextInitializer() { 38 | return new ServletContextInitializer() { 39 | 40 | @Override 41 | public void onStartup(ServletContext servletContext) 42 | throws ServletException { 43 | servletContext.setInitParameter("contextClass","com.example.osgi.framework.web.OsgiBundleXmlWebApplicationContext"); 44 | DispatcherSetup.logger.info("NEW SERVLET CONTEXT INIALIZED AS = " + servletContext.toString()); 45 | } 46 | }; 47 | } 48 | 49 | /** 50 | * Register dispatcherServlet programmatically 51 | * 52 | * @return ServletRegistrationBean 53 | */ 54 | @Bean 55 | public ServletRegistrationBean dispatcherServletRegistration() { 56 | 57 | ServletRegistrationBean registration = new ServletRegistrationBean( 58 | dispatcherServlet(), 59 | "/dispatch/*" 60 | ); 61 | 62 | //registration.addInitParameter("contextClass","com.example.osgi.framework.web.OsgiBundleXmlWebApplicationContext"); 63 | 64 | registration.setName( 65 | DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME 66 | ); 67 | 68 | return registration; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/example/osgi/framework/web/MainErrorController.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.framework.web; 2 | 3 | import javax.servlet.RequestDispatcher; 4 | import javax.servlet.http.HttpServletRequest; 5 | 6 | import org.springframework.boot.web.servlet.error.ErrorController; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | 11 | @Controller 12 | public class MainErrorController implements ErrorController { 13 | 14 | // TODO: If we are in startup mode then show a progress bar and keep checking to see 15 | // If were started yet then refresh when we are. 16 | @RequestMapping("/error") 17 | public String handleError(HttpServletRequest request) { 18 | Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); 19 | 20 | if (status != null) { 21 | Integer statusCode = Integer.valueOf(status.toString()); 22 | 23 | if(statusCode == HttpStatus.NOT_FOUND.value()) { 24 | return "error-404"; 25 | } 26 | else if(statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) { 27 | return "error-500"; 28 | } 29 | } 30 | return "error"; 31 | } 32 | 33 | @Override 34 | public String getErrorPath() { 35 | return "/error"; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/example/osgi/framework/web/OsgiBundleXmlWebApplicationContext.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.framework.web; 2 | 3 | import java.io.IOException; 4 | import java.util.Map; 5 | 6 | import javax.servlet.ServletConfig; 7 | import javax.servlet.ServletContext; 8 | 9 | import org.osgi.framework.Bundle; 10 | import org.osgi.framework.BundleContext; 11 | import org.osgi.framework.ServiceReference; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 15 | import org.springframework.context.ApplicationContext; 16 | import org.springframework.context.ApplicationContextException; 17 | import org.springframework.core.env.ConfigurableEnvironment; 18 | import org.springframework.core.io.Resource; 19 | import org.eclipse.gemini.blueprint.context.ConfigurableOsgiBundleApplicationContext; 20 | import org.eclipse.gemini.blueprint.context.support.OsgiBundleXmlApplicationContext; 21 | import org.eclipse.gemini.blueprint.extender.OsgiBeanFactoryPostProcessor; 22 | import org.springframework.ui.context.Theme; 23 | import org.springframework.ui.context.ThemeSource; 24 | import org.springframework.ui.context.support.UiApplicationContextUtils; 25 | import org.springframework.util.Assert; 26 | import org.springframework.util.ObjectUtils; 27 | import org.springframework.util.StringUtils; 28 | import org.springframework.web.context.ConfigurableWebApplicationContext; 29 | import org.springframework.web.context.ServletConfigAware; 30 | import org.springframework.web.context.ServletContextAware; 31 | import org.springframework.web.context.support.AbstractRefreshableWebApplicationContext; 32 | import org.springframework.web.context.support.ServletContextAwareProcessor; 33 | import org.springframework.web.context.support.ServletContextResourcePatternResolver; 34 | import org.springframework.web.context.support.StandardServletEnvironment; 35 | import org.springframework.web.context.support.WebApplicationContextUtils; 36 | import org.springframework.web.context.support.XmlWebApplicationContext; 37 | 38 | /** 39 | * ServerOsgiBundleXmlWebApplicationContext is a custom extension of 40 | * {@link OsgiBundleXmlApplicationContext} which provides support for application contexts backed by an OSGi 41 | * {@link Bundle bundle} in Spring MVC based web applications by implementing {@link ConfigurableWebApplicationContext}. 42 | *

43 | * 44 | * Since Java does not support multiple inheritance, the implementation details specific to 45 | * ConfigurableWebApplicationContext have been copied directly from {@link XmlWebApplicationContext} and 46 | * {@link AbstractRefreshableWebApplicationContext}. 47 | *

48 | * 49 | * Concurrent Semantics
50 | * 51 | * This class is not thread-safe. 52 | * 53 | */ 54 | public class OsgiBundleXmlWebApplicationContext extends OsgiBundleXmlApplicationContext implements ConfigurableWebApplicationContext, 55 | ThemeSource { 56 | 57 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 58 | 59 | /** 60 | * {@link ServletContext} attribute name for the {@link BundleContext} to be used to back this 61 | * {@link org.eclipse.gemini.blueprint.context.ConfigurableOsgiBundleApplicationContext 62 | * ConfigurableOsgiBundleApplicationContext}. 63 | */ 64 | public static final String BUNDLE_CONTEXT_ATTRIBUTE = "osgi-bundlecontext"; 65 | 66 | /** service entry used for storing the namespace associated with this context */ 67 | private static final String APPLICATION_CONTEXT_SERVICE_NAMESPACE_PROPERTY = "org.springframework.web.context.namespace"; 68 | 69 | /** Suffix for WebApplicationContext namespaces. */ 70 | private static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet"; 71 | 72 | private static final String PREFIX_DELIMITER = ":"; 73 | 74 | /** Servlet context that this context runs in */ 75 | private ServletContext servletContext; 76 | 77 | /** ResourcePatternResolver for the associated ServletContext. */ 78 | private ServletContextResourcePatternResolver servletContextResourcePatternResolver; 79 | 80 | /** Servlet config that this context runs in, if any */ 81 | private ServletConfig servletConfig; 82 | 83 | /** Namespace of this context, or null if root */ 84 | private String namespace; 85 | 86 | /** the ThemeSource for this ApplicationContext */ 87 | private ThemeSource themeSource; 88 | 89 | /** 90 | * Creates a ServerOsgiBundleXmlWebApplicationContext with no parent. 91 | */ 92 | public OsgiBundleXmlWebApplicationContext() { 93 | super(); 94 | setDisplayName("Root ServerOsgiBundleXmlWebApplicationContext"); 95 | } 96 | 97 | /** 98 | * Creates a ServerOsgiBundleXmlWebApplicationContext with the supplied config locations. 99 | * 100 | * @param configLocations the config locations. 101 | */ 102 | public OsgiBundleXmlWebApplicationContext(String[] configLocations) { 103 | super(configLocations); 104 | setDisplayName("Root ServerOsgiBundleXmlWebApplicationContext"); 105 | logger.debug("Creating an ServerOsgiBundleXmlWebApplicationContext with locations [{}].", ObjectUtils.nullSafeToString(configLocations)); 106 | } 107 | 108 | /** 109 | * Creates a ServerOsgiBundleXmlWebApplicationContext with the supplied parent. 110 | * 111 | * @param parent the parent {@link ApplicationContext}. 112 | */ 113 | public OsgiBundleXmlWebApplicationContext(ApplicationContext parent) { 114 | super(parent); 115 | setDisplayName("Root ServerOsgiBundleXmlWebApplicationContext"); 116 | logger.debug("Creating an ServerOsgiBundleXmlWebApplicationContext with parent [{}].", parent); 117 | } 118 | 119 | /** 120 | * Creates a ServerOsgiBundleXmlWebApplicationContext with the supplied parent and config locations. 121 | * 122 | * @param configLocations the config locations. 123 | * @param parent the parent {@link ApplicationContext}. 124 | */ 125 | public OsgiBundleXmlWebApplicationContext(String[] configLocations, ApplicationContext parent) { 126 | super(configLocations, parent); 127 | setDisplayName("Root ServerOsgiBundleXmlWebApplicationContext"); 128 | logger.debug("Creating an ServerOsgiBundleXmlWebApplicationContext with locations [{}] and parent [{}].", 129 | ObjectUtils.nullSafeToString(configLocations), parent); 130 | } 131 | 132 | /** 133 | * Determines if the supplied location does not have a prefix. 134 | */ 135 | protected static boolean hasNoPrefix(String location) { 136 | return location == null || location.indexOf(PREFIX_DELIMITER) < 1; 137 | } 138 | 139 | /** 140 | * {@inheritDoc} 141 | */ 142 | public void setServletContext(final ServletContext servletContext) { 143 | 144 | this.servletContext = servletContext; 145 | this.servletContextResourcePatternResolver = new ServletContextResourcePatternResolver(servletContext); 146 | 147 | // If the BundleContext has not yet been set, attempt to retrieve it from the ServletContext or the parent 148 | // ApplicationContext. 149 | if (getBundleContext() == null) { 150 | getBundleContext(); 151 | } 152 | } 153 | 154 | /** 155 | * {@inheritDoc} 156 | */ 157 | @Override 158 | public BundleContext getBundleContext() { 159 | BundleContext bundleContext = super.getBundleContext(); 160 | if (bundleContext == null) { 161 | 162 | // Attempt to locate the BundleContext in the ServletContext 163 | if (this.servletContext != null) { 164 | Object bundleContextFromServletContext = this.servletContext.getAttribute(BUNDLE_CONTEXT_ATTRIBUTE); 165 | if (bundleContextFromServletContext != null) { 166 | Assert.isInstanceOf(BundleContext.class, bundleContextFromServletContext); 167 | this.logger.debug("Using the BundleContext stored in the ServletContext as '{}'.", BUNDLE_CONTEXT_ATTRIBUTE); 168 | bundleContext = (BundleContext) bundleContextFromServletContext; 169 | setBundleContext(bundleContext); 170 | } 171 | } 172 | 173 | // If still not set, fall back to the parent 174 | if (bundleContext == null) { 175 | ApplicationContext parent = getParent(); 176 | if (parent instanceof ConfigurableOsgiBundleApplicationContext) { 177 | this.logger.debug("Using the parent ApplicationContext's BundleContext"); 178 | bundleContext = ((ConfigurableOsgiBundleApplicationContext) parent).getBundleContext(); 179 | setBundleContext(bundleContext); 180 | } 181 | } 182 | } 183 | return bundleContext; 184 | } 185 | 186 | 187 | /** 188 | * {@inheritDoc} 189 | */ 190 | public ServletContext getServletContext() { 191 | return this.servletContext; 192 | } 193 | 194 | /** 195 | * {@inheritDoc} 196 | */ 197 | public void setServletConfig(ServletConfig servletConfig) { 198 | this.servletConfig = servletConfig; 199 | if (servletConfig != null) { 200 | if (getServletContext() == null) { 201 | setServletContext(servletConfig.getServletContext()); 202 | } 203 | if (getNamespace() == null) { 204 | setNamespace(servletConfig.getServletName() + DEFAULT_NAMESPACE_SUFFIX); 205 | } 206 | } 207 | } 208 | 209 | /** 210 | * {@inheritDoc} 211 | */ 212 | public ServletConfig getServletConfig() { 213 | return this.servletConfig; 214 | } 215 | 216 | /** 217 | * Set the config locations for this application context in init-param style, i.e. with distinct locations separated 218 | * by commas, semicolons or whitespace. 219 | *

220 | * If not set, the implementation may use a default as appropriate. 221 | */ 222 | public void setConfigLocation(String location) { 223 | setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS)); 224 | } 225 | 226 | /** 227 | * The default location for the root context is "/WEB-INF/applicationContext.xml", and "/WEB-INF/test-servlet.xml" 228 | * for a context with the namespace "test-servlet" (like for a DispatcherServlet instance with the servlet-name 229 | * "test"). 230 | */ 231 | @Override 232 | protected String[] getDefaultConfigLocations() { 233 | String ns = getNamespace(); 234 | if (ns != null) { 235 | return new String[] { XmlWebApplicationContext.DEFAULT_CONFIG_LOCATION_PREFIX + ns 236 | + XmlWebApplicationContext.DEFAULT_CONFIG_LOCATION_SUFFIX }; 237 | } else { 238 | return new String[] { XmlWebApplicationContext.DEFAULT_CONFIG_LOCATION }; 239 | } 240 | } 241 | 242 | /** 243 | * {@inheritDoc} 244 | */ 245 | public void setNamespace(String namespace) { 246 | this.namespace = namespace; 247 | if (namespace != null) { 248 | setDisplayName("ServerOsgiBundleXmlWebApplicationContext for namespace '" + namespace + "'"); 249 | } 250 | } 251 | 252 | /** 253 | * {@inheritDoc} 254 | */ 255 | public String getNamespace() { 256 | return this.namespace; 257 | } 258 | 259 | /** 260 | * Register request/session scopes, a {@link ServletContextAwareProcessor}, etc. 261 | * 262 | * @see WebApplicationContextUtils#registerWebApplicationScopes(ConfigurableListableBeanFactory) 263 | */ 264 | @Override 265 | protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { 266 | super.postProcessBeanFactory(beanFactory); 267 | 268 | // Drive the kernel's bean factory post processors. 269 | BundleContext bundleContext = getBundleContext(); 270 | if (bundleContext != null) { 271 | ServiceReference sr = bundleContext.getServiceReference(OsgiBeanFactoryPostProcessor.class); 272 | if (sr != null) { 273 | OsgiBeanFactoryPostProcessor kernelPostProcessor = bundleContext.getService(sr); 274 | try { 275 | kernelPostProcessor.postProcessBeanFactory(bundleContext, beanFactory); 276 | } catch (Exception e) { 277 | throw new ApplicationContextException("Kernel bean factory post processor failed", e); 278 | } finally { 279 | bundleContext.ungetService(sr); 280 | } 281 | } 282 | } 283 | 284 | beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(getServletContext(), getServletConfig())); 285 | beanFactory.ignoreDependencyInterface(ServletContextAware.class); 286 | beanFactory.ignoreDependencyInterface(ServletConfigAware.class); 287 | beanFactory.registerResolvableDependency(ServletContext.class, getServletContext()); 288 | beanFactory.registerResolvableDependency(ServletConfig.class, getServletConfig()); 289 | 290 | WebApplicationContextUtils.registerWebApplicationScopes(beanFactory); 291 | } 292 | 293 | /** 294 | * {@inheritDoc} 295 | * 296 | * Additionally, this implementation publishes the context namespace under the 297 | * org.springframework.web.context.namespace property. 298 | */ 299 | @SuppressWarnings("unchecked") 300 | @Override 301 | protected void customizeApplicationContextServiceProperties(Map serviceProperties) { 302 | super.customizeApplicationContextServiceProperties(serviceProperties); 303 | String ns = getNamespace(); 304 | if (ns != null) { 305 | serviceProperties.put(APPLICATION_CONTEXT_SERVICE_NAMESPACE_PROPERTY, ns); 306 | } 307 | } 308 | 309 | /** 310 | * Returns a {@link org.springframework.web.context.support.ServletContextResource ServletContextResource} if the supplied location does not have a prefix and 311 | * otherwise delegates to the parent implementation for standard Spring DM resource loading semantics. 312 | *

313 | * This override is necessary to return a suitable {@link Resource} type for flow-relative views. See DMS-2310. 314 | */ 315 | @Override 316 | public Resource getResource(String location) { 317 | if (hasNoPrefix(location)) { 318 | return this.servletContextResourcePatternResolver.getResource(location); 319 | } 320 | return super.getResource(location); 321 | } 322 | 323 | /** 324 | * Returns an array of {@link org.springframework.web.context.support.ServletContextResource ServletContextResource}s if the supplied locationPattern does 325 | * not have a prefix and otherwise delegates to the parent implementation for standard Spring DM resource loading 326 | * semantics. 327 | *

328 | * This override is necessary to return a suitable {@link Resource} type for flow-relative views. See DMS-2310. 329 | */ 330 | @Override 331 | public Resource[] getResources(String locationPattern) throws IOException { 332 | if (hasNoPrefix(locationPattern)) { 333 | return this.servletContextResourcePatternResolver.getResources(locationPattern); 334 | } 335 | return super.getResources(locationPattern); 336 | } 337 | 338 | /** 339 | * Initialize the theme capability. 340 | */ 341 | @Override 342 | protected void onRefresh() { 343 | this.themeSource = UiApplicationContextUtils.initThemeSource(this); 344 | } 345 | 346 | /** 347 | * {@inheritDoc} 348 | */ 349 | public Theme getTheme(String themeName) { 350 | return this.themeSource.getTheme(themeName); 351 | } 352 | 353 | /** 354 | * {@inheritDoc} 355 | */ 356 | @Override 357 | protected ConfigurableEnvironment createEnvironment() { 358 | return new StandardServletEnvironment(); 359 | } 360 | 361 | } -------------------------------------------------------------------------------- /src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | # -------------------------------- 2 | # Spring Settings 3 | # -------------------------------- 4 | # override the profile with -Dspring.profiles.active=development 5 | #spring.profiles.active=development 6 | server.error.whitelabel.enabled=false 7 | 8 | # -------------------------------- 9 | # Spring Web Settings 10 | # -------------------------------- 11 | server.port=8080 12 | server.address=0.0.0.0 13 | server.http2.enabled=true 14 | #server.servlet.context-path=/portal 15 | # -------------------------------- 16 | # Logging Config 17 | # -------------------------------- 18 | logging.config=classpath:log4j-dev.xml 19 | # configure specific rules here 20 | #logging.level.org.springframework.web=ERROR 21 | 22 | # -------------------------------- 23 | # Felix Framework settings 24 | # -------------------------------- 25 | # If true will configure a shutdown hook to shutdown the framework on JVM shutdown 26 | felix.shutdown.hook=true 27 | # Where is the felix bundle cache dir 28 | org.osgi.framework.storage=build/felix-cache 29 | # When to clean the felix-cache directory 30 | org.osgi.framework.storage.clean=onFirstInit 31 | # Directory to look for bundles to deploy once when framework first loads 32 | felix.auto.deploy.dir=build/framework_bundles 33 | # Actions to perform on discovered bundles 34 | felix.auto.deploy.action=install,start 35 | felix.startlevel.bundle=1 36 | 37 | # -------------------------------- 38 | # Felix FileInstall bundle settings 39 | # -------------------------------- 40 | 41 | # Number of milliseconds between 2 polls of the directory 42 | felix.fileinstall.poll=2000 43 | # The name of the directory to watch. Several directories can be specified by using a comma separated list. 44 | felix.fileinstall.dir=build/plugins 45 | # Threshold of logging information 46 | felix.fileinstall.log.level=1 47 | # Automatically start newly discovered bundles 48 | felix.fileinstall.bundles.new.start=true 49 | # A regular expression to be used to filter file names 50 | felix.fileinstall.filter=.* 51 | # The name of the temporary directory to use with exploded or transformed bundles (defaults to the temporary dir for the JVM) 52 | felix.fileinstall.tmpdir=./temp 53 | # Determines if File Install waits felix.fileinstall.poll milliseconds before doing an initial scan or not. 54 | felix.fileinstall.noInitialDelay=false 55 | # If true, File Install will start the bundles transiently. 56 | felix.fileinstall.bundles.startTransient=false 57 | # Start bundles with their default activation policy or not 58 | felix.fileinstall.bundles.startActivationPolicy=true 59 | # If set to a value different from 0, File Install will set the start level for deployed bundles to that value. 60 | # If set to 0, the default framework bundle start level will be used. 61 | felix.fileinstall.start.level=2 62 | # FileInstall won't scan for files unless the active framework start level is greater than this value 63 | felix.fileinstall.active.level=0 64 | # If false, File Install will not write back in the file configurations changes. 65 | # Note that this setting is global and can not be modified per polled directory. 66 | felix.fileinstall.enableConfigSave=true 67 | # If true, FileInstall will update managed bundles when a new version of an artifact handler is detected. 68 | # This detection is performed based on the last modification date of the bundle registering the handler. 69 | felix.fileinstall.bundles.updateWithListeners=true 70 | # Possible values are jar, skip, recurse. Defines the behavior for sub directories. 71 | felix.fileinstall.subdir.mode=jar 72 | 73 | # -------------------------------- 74 | # Jetty Settings 75 | # -------------------------------- 76 | # Host name or IP Address of the interface to listen on. The default is null causing Jetty to listen on all interfaces. 77 | org.apache.felix.http.host=0.0.0.0 78 | # The Servlet Context Path to use for the Http Service. If this property is not configured it defaults to "/". 79 | # This must be a valid path starting with a slash and not ending with a slash (unless it is the root context). 80 | org.apache.felix.http.context_path=/ 81 | # Connection timeout in milliseconds. The default is 60000 (60 seconds). 82 | org.apache.felix.http.timeout=60000 83 | # - Allows for the specification of the Session life time as a number of minutes. 84 | # This property serves the same purpose as the session-timeout element in a Web Application descriptor. 85 | # The default is zero for no timeout at all. 86 | org.apache.felix.http.session.timeout=0 87 | # Name of the cookie used to transport the Session ID. The default is JSESSIONID. 88 | org.mortbay.jetty.servlet.SessionCookie=JSESSIONID 89 | # Name of the request parameter to transport the Session ID. The default is jsessionid. 90 | org.mortbay.jetty.servlet.SessionURL=jsessionid 91 | # Domain to set on the session cookie. The default is null. 92 | org.mortbay.jetty.servlet.SessionDomain= 93 | # The path to set on the session cookie. The default is the configured session context path (/). 94 | org.mortbay.jetty.servlet.SessionPath=/ 95 | # The maximum age value to set on the cookie. The default is -1. 96 | org.mortbay.jetty.servlet.MaxAge=-1 97 | # Size of the buffer for request and response headers. Default is 16KB. 98 | org.apache.felix.http.jetty.headerBufferSize 99 | # Size of the buffer for requests not fitting the header buffer. Default is 8KB. 100 | org.apache.felix.http.jetty.requestBufferSize 101 | # Size of the buffer for responses. Default is 24KB. 102 | org.apache.felix.http.jetty.responseBufferSize -------------------------------------------------------------------------------- /src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | # -------------------------------- 2 | # Spring Settings 3 | # -------------------------------- 4 | # override the profile with -Dspring.profiles.active=development 5 | #spring.profiles.active=production 6 | #spring.application.name= 7 | #server.error.whitelabel.enabled=false 8 | 9 | # -------------------------------- 10 | # Spring Web Settings 11 | # -------------------------------- 12 | server.port=8000 13 | server.address=0.0.0.0 14 | server.http2.enabled=true 15 | server.servlet.context-path=/portal 16 | 17 | # -------------------------------- 18 | # Logging Config 19 | # -------------------------------- 20 | logging.config=classpath:log4j.xml 21 | # configure specific rules here 22 | #logging.level.org.springframework.web=ERROR 23 | 24 | # -------------------------------- 25 | # Felix Framework settings 26 | # -------------------------------- 27 | # If true will configure a shutdown hook to shutdown the framework on JVM shutdown 28 | felix.shutdown.hook=true 29 | # Where is the felix bundle cache dir 30 | org.osgi.framework.storage=felix-cache 31 | # When to clean the felix-cache directory 32 | org.osgi.framework.storage.clean=onFirstInit 33 | # Directory to look for bundles to deploy once when framework first loads 34 | felix.auto.deploy.dir=framework_bundles 35 | # Actions to perform on discovered bundles 36 | felix.auto.deploy.action=install,start 37 | 38 | # -------------------------------- 39 | # Felix FileInstall bundle settings 40 | # -------------------------------- 41 | 42 | # Number of milliseconds between 2 polls of the directory 43 | felix.fileinstall.poll=2000 44 | # The name of the directory to watch. Several directories can be specified by using a comma separated list. 45 | felix.fileinstall.dir=plugins 46 | # Threshold of logging information 47 | felix.fileinstall.log.level=1 48 | # Automatically start newly discovered bundles 49 | felix.fileinstall.bundles.new.start=true 50 | # A regular expression to be used to filter file names 51 | felix.fileinstall.filter=.* 52 | # The name of the temporary directory to use with exploded or transformed bundles (defaults to the temporary dir for the JVM) 53 | felix.fileinstall.tmpdir=temp 54 | # Determines if File Install waits felix.fileinstall.poll milliseconds before doing an initial scan or not. 55 | felix.fileinstall.noInitialDelay=false 56 | # If true, File Install will start the bundles transiently. 57 | felix.fileinstall.bundles.startTransient=false 58 | # Start bundles with their default activation policy or not 59 | felix.fileinstall.bundles.startActivationPolicy=true 60 | # If set to a value different from 0, File Install will set the start level for deployed bundles to that value. 61 | # If set to 0, the default framework bundle start level will be used. 62 | felix.fileinstall.start.level=0 63 | # FileInstall won't scan for files unless the active framework start level is greater than this value 64 | felix.fileinstall.active.level=0 65 | # If false, File Install will not write back in the file configurations changes. 66 | # Note that this setting is global and can not be modified per polled directory. 67 | felix.fileinstall.enableConfigSave=true 68 | # If true, FileInstall will update managed bundles when a new version of an artifact handler is detected. 69 | # This detection is performed based on the last modification date of the bundle registering the handler. 70 | felix.fileinstall.bundles.updateWithListeners=true 71 | # Possible values are jar, skip, recurse. Defines the behavior for sub directories. 72 | felix.fileinstall.subdir.mode=jar 73 | 74 | # -------------------------------- 75 | # Jetty Settings 76 | # -------------------------------- 77 | # Host name or IP Address of the interface to listen on. The default is null causing Jetty to listen on all interfaces. 78 | org.apache.felix.http.host=0.0.0.0 79 | # The Servlet Context Path to use for the Http Service. If this property is not configured it defaults to "/". 80 | # This must be a valid path starting with a slash and not ending with a slash (unless it is the root context). 81 | org.apache.felix.http.context_path=/ 82 | # Connection timeout in milliseconds. The default is 60000 (60 seconds). 83 | org.apache.felix.http.timeout=60000 84 | # - Allows for the specification of the Session life time as a number of minutes. 85 | # This property serves the same purpose as the session-timeout element in a Web Application descriptor. 86 | # The default is zero for no timeout at all. 87 | org.apache.felix.http.session.timeout=0 88 | # Name of the cookie used to transport the Session ID. The default is JSESSIONID. 89 | org.mortbay.jetty.servlet.SessionCookie=JSESSIONID 90 | # Name of the request parameter to transport the Session ID. The default is jsessionid. 91 | org.mortbay.jetty.servlet.SessionURL=jsessionid 92 | # Domain to set on the session cookie. The default is null. 93 | org.mortbay.jetty.servlet.SessionDomain= 94 | # The path to set on the session cookie. The default is the configured session context path (/). 95 | org.mortbay.jetty.servlet.SessionPath=/ 96 | # The maximum age value to set on the cookie. The default is -1. 97 | org.mortbay.jetty.servlet.MaxAge=-1 98 | # Size of the buffer for request and response headers. Default is 16KB. 99 | org.apache.felix.http.jetty.headerBufferSize 100 | # Size of the buffer for requests not fitting the header buffer. Default is 8KB. 101 | org.apache.felix.http.jetty.requestBufferSize 102 | # Size of the buffer for responses. Default is 24KB. 103 | org.apache.felix.http.jetty.responseBufferSize -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ______ ______ ______ ______ ______ __ 2 | /\ == \ /\ __ \ /\ == \ /\__ _\ /\ __ \ /\ \ 3 | \ \ _-/ \ \ \/\ \ \ \ __< \/_/\ \/ \ \ __ \ \ \ \____ 4 | \ \_\ \ \_____\ \ \_\ \_\ \ \_\ \ \_\ \_\ \ \_____\ 5 | \/_/ \/_____/ \/_/ /_/ \/_/ \/_/\/_/ \/_____/ 6 | -------------------------------------------------------------------------------- /src/main/resources/example.felix.properties: -------------------------------------------------------------------------------- 1 | # Sets the OSGi execution environment for the framework. 2 | # The framework tries to set this to a reasonable default value. 3 | # If you specify a value, it overrides the framework default. 4 | # Refer to the OSGi specification for appropriate execution environment values. 5 | 6 | #org.osgi.framework.executionenvironment= 7 | 8 | # Sets the directory to use as the bundle cache; by default the bundle cache directory is felix-cache in the current working directory. 9 | # The value should be a valid directory name. The directory name can be either absolute or relative. 10 | # Relative directory names are relative to the current working directory. The specified directory will be created if it does not exist. 11 | 12 | #org.osgi.framework.storage= 13 | 14 | # Sets the root directory used to calculate the bundle cache directory for relative directory names. 15 | # If org.osgi.framework.storage is set to a relative name, by default it is relative to the current working directory. 16 | # If this property is set, then it will be calculated as being relative to the specified root directory. 17 | 18 | #felix.cache.rootdir= 19 | 20 | # Determines whether the bundle cache is flushed. 21 | # The value can either be "none" or "onFirstInit", where "none" does not flush the bundle cache and "onFirstInit" flushes the bundle cache 22 | # when the framework instance is first initialized. The default value is "none". 23 | 24 | #org.osgi.framework.storage.clean= 25 | 26 | # The integer value of this string sets an upper limit on how many files the cache will open. 27 | # The default value is zero, which means there is no limit. (Since 4.0) 28 | 29 | #felix.cache.filelimit= 30 | 31 | # Enables or disables bundle cache locking, which is used to prevent concurrent access to the bundle cache. 32 | # This is enabled by default, but on older/smaller JVMs file channel locking is not available; set this property to false to disable it. 33 | 34 | #felix.cache.locking= 35 | 36 | # Sets the buffer size to be used by the cache; the default value is 4096. 37 | # The integer value of this string provides control over the size of the internal buffer of the disk cache for performance reasons. 38 | 39 | #felix.cache.bufsize= 40 | 41 | # Specifies a comma-delimited list of packages that should be exported via the System Bundle from the framework class loader. 42 | # The framework will set this to a reasonable default. If the value is specified, it replaces any default value. 43 | 44 | #org.osgi.framework.system.packages= 45 | 46 | # Specifies a comma-delimited list of packages that should be exported via the System Bundle from the framework class loader in addition 47 | # to the packages in org.osgi.framework.system.packages. The default value is empty. If a value is specified, 48 | # it is appended to the list of default or specified packages in org.osgi.framework.system.packages. 49 | 50 | #org.osgi.framework.system.packages.extra= 51 | 52 | # Specifies a comma-delimited list of packages that should be made implicitly available to all bundles from the parent class loader. 53 | # It is recommended not to use this property since it breaks modularity. The default value is empty. 54 | 55 | #org.osgi.framework.bootdelegation= 56 | 57 | # Specifies which class loader is used for boot delegation. Possible values are: boot for the boot class loader, 58 | # app for the application class loader, ext for the extension class loader, and framework for the framework's class loader. The default is boot. 59 | 60 | #org.osgi.framework.bundle.parent= 61 | 62 | # - Specifies whether the framework should try to guess when to implicitly boot delegate to ease integration with external code. 63 | # The default value is true. 64 | 65 | #felix.bootdelegation.implicit= 66 | 67 | # - A List of BundleActivator instances that are started/stopped when the System Bundle is started/stopped. 68 | # The specified instances will receive the System Bundle's BundleContext when invoked. 69 | # (This property cannot be set in the configuration file since it requires instances; it can only be passed into Felix' constructor directly.) 70 | 71 | #felix.systembundle.activators= 72 | 73 | # - An instance of org.apache.felix.framework.Logger that the framework uses as its default logger. 74 | # (This property cannot be set in the configuration file since it requires an instance; it can only be passed into Felix' constructor directly.) 75 | 76 | #felix.log.logger= 77 | 78 | # - An integer value indicating the degree of logging reported by the framework; the higher the value the more logging is reported. 79 | # If zero ('0') is specified, then logging is turned off completely. 80 | # The log levels match those specified in the OSGi Log Service (i.e., 1 = error, 2 = warning, 3 = information, and 4 = debug). 81 | # The default value is 1. 82 | 83 | #felix.log.level=4 84 | 85 | # The initial start level of the framework once it starts execution; the default value is 1. 86 | 87 | #org.osgi.framework.startlevel.beginning= 88 | 89 | # The default start level for newly installed bundles; the default value is 1. 90 | 91 | #felix.startlevel.bundle= 92 | 93 | # Flag to indicate whether to activate the URL Handlers service for the framework instance; the default value is true. 94 | # Activating the URL Handlers service will result in the URL.setURLStreamHandlerFactory() and URLConnection.setContentHandlerFactory() being called. 95 | 96 | #felix.service.urlhandlers= 97 | 98 | # - An alias for resolving native processor requirements new with R6. This can be used to add new or override existing processors. 99 | # The represents the key for the processor architecture such as x86-64 (which may not contain spaces) 100 | # then the property value is a comma delimited list of aliases such as x86_64,amd64. EX felix.native.processor.alias.x86-64=x86_64,amd64 101 | 102 | #felix.native.processor.alias.= 103 | 104 | # - An alias for resolving native operating system requirements new with R6. This can be used to add new or override existing operating systems. 105 | # The represents the key for the operating system such as windows7 (which may not contain spaces) 106 | # then the property value is a comma delimited list of aliases such as Windows 7,win32. EX felix.native.osname.alias.windows7=Windows 7,win32 107 | 108 | #felix.native.osname.alias.= 109 | -------------------------------------------------------------------------------- /src/main/resources/log4j-dev.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger{36}.%M - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger{36}.%M - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/messages_en.properties: -------------------------------------------------------------------------------- 1 | #Spring boot greeting message 2 | message.greetings=Welcome to the Bad Ass OSGI Spring Boot portal of doom! -------------------------------------------------------------------------------- /src/main/resources/templates/error-404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Na! that doesnt exist ( yet ) :-)

5 |

Try again later

6 | Go Home 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/templates/error-500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Server Died (500)!

5 |

Our Engineers are on it

6 | Go Home 7 | 8 | -------------------------------------------------------------------------------- /src/main/templates/start.sh.template: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## @name@ start up script for UN*X 6 | ## This script was auto generated on @generated@. 7 | ## 8 | ############################################################################## 9 | 10 | # Attempt to set APP_HOME 11 | # Resolve links: $0 may be a link 12 | PRG="$0" 13 | 14 | # Need this for relative symlinks. 15 | while [ -h "$PRG" ] ; do 16 | ls=`ls -ld "$PRG"` 17 | link=`expr "$ls" : '.*-> \(.*\)$'` 18 | if expr "$link" : '/.*' > /dev/null; then 19 | PRG="$link" 20 | else 21 | PRG=`dirname "$PRG"`"/$link" 22 | fi 23 | done 24 | 25 | SAVED="`pwd`" 26 | cd "`dirname \"$PRG\"`/.." >/dev/null 27 | APP_HOME="`pwd -P`" 28 | cd "$SAVED" >/dev/null 29 | 30 | APP_NAME="@name@" 31 | APP_BASE_NAME=`basename "$0"` 32 | 33 | # Add default JVM options here. You can also use JAVA_OPTS and @extra_java_opts_property@ to pass JVM options to this script. 34 | DEFAULT_JVM_OPTS="" 35 | @extra_java_opts_property@="@extra_java_opts@" 36 | 37 | # Use the maximum available, or set MAX_FD != -1 to use that value. 38 | MAX_FD="maximum" 39 | 40 | warn ( ) { 41 | echo "$*" 42 | } 43 | 44 | die ( ) { 45 | echo 46 | echo "$*" 47 | echo 48 | exit 1 49 | } 50 | 51 | # OS specific support (must be 'true' or 'false'). 52 | cygwin=false 53 | msys=false 54 | darwin=false 55 | nonstop=false 56 | case "`uname`" in 57 | CYGWIN* ) 58 | cygwin=true 59 | ;; 60 | Darwin* ) 61 | darwin=true 62 | ;; 63 | MINGW* ) 64 | msys=true 65 | ;; 66 | NONSTOP* ) 67 | nonstop=true 68 | ;; 69 | esac 70 | 71 | JARPATH=$APP_HOME/lib/@name@-@version@.jar 72 | 73 | # Determine the Java command to use to start the JVM. 74 | if [ -n "$JAVA_HOME" ] ; then 75 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 76 | # IBM's JDK on AIX uses strange locations for the executables 77 | JAVACMD="$JAVA_HOME/jre/sh/java" 78 | else 79 | JAVACMD="$JAVA_HOME/bin/java" 80 | fi 81 | if [ ! -x "$JAVACMD" ] ; then 82 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 83 | 84 | Please set the JAVA_HOME variable in your environment to match the 85 | location of your Java installation." 86 | fi 87 | else 88 | JAVACMD="java" 89 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 90 | 91 | Please set the JAVA_HOME variable in your environment to match the 92 | location of your Java installation." 93 | fi 94 | 95 | # Increase the maximum file descriptors if we can. 96 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 97 | MAX_FD_LIMIT=`ulimit -H -n` 98 | if [ $? -eq 0 ] ; then 99 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 100 | MAX_FD="$MAX_FD_LIMIT" 101 | fi 102 | ulimit -n $MAX_FD 103 | if [ $? -ne 0 ] ; then 104 | warn "Could not set maximum file descriptor limit: $MAX_FD" 105 | fi 106 | else 107 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 108 | fi 109 | fi 110 | 111 | # For Darwin, add options to specify how the application appears in the dock 112 | if $darwin; then 113 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 114 | fi 115 | 116 | # For Cygwin, switch paths to Windows format before running java 117 | if $cygwin ; then 118 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 119 | JARPATH=`cygpath --path --mixed "$JARPATH"` 120 | JAVACMD=`cygpath --unix "$JAVACMD"` 121 | 122 | # We build the pattern for arguments to be converted via cygpath 123 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 124 | SEP="" 125 | for dir in $ROOTDIRSRAW ; do 126 | ROOTDIRS="$ROOTDIRS$SEP$dir" 127 | SEP="|" 128 | done 129 | OURCYGPATTERN="(^($ROOTDIRS))" 130 | # Add a user-defined pattern to the cygpath arguments 131 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 132 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 133 | fi 134 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 135 | i=0 136 | for arg in "$@" ; do 137 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 138 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 139 | 140 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 141 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 142 | else 143 | eval `echo args$i`="\"$arg\"" 144 | fi 145 | i=$((i+1)) 146 | done 147 | case $i in 148 | (0) set -- ;; 149 | (1) set -- "$args0" ;; 150 | (2) set -- "$args0" "$args1" ;; 151 | (3) set -- "$args0" "$args1" "$args2" ;; 152 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 153 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 154 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 155 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 156 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 157 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 158 | esac 159 | fi 160 | 161 | # Escape application args 162 | save ( ) { 163 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 164 | echo " " 165 | } 166 | APP_ARGS=$(save "$@") 167 | 168 | # Collect all arguments for the java command, following the shell quoting and substitution rules 169 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $@extra_java_opts_property@ -jar "\"$JARPATH\"" "$APP_ARGS" 170 | 171 | # Make sure we are actually in the APP_HOME dir for CWD purposes 172 | cd $APP_HOME 173 | 174 | echo "--------------------------------------------------------------" 175 | echo " HOME=$HOME" 176 | echo " PWD=$PWD" 177 | echo " APP_HOME=$APP_HOME" 178 | echo " APP_NAME=$APP_NAME" 179 | echo " APP_BASE_NAME=$APP_BASE_NAME" 180 | echo " DEFAULT_JVM_OPTS=$DEFAULT_JVM_OPTS" 181 | echo " JAVA_OPTS=$JAVA_OPTS" 182 | echo " @extra_java_opts_property@=$@extra_java_opts_property@" 183 | echo " CMD=$JAVACMD $@" 184 | echo "--------------------------------------------------------------" 185 | 186 | exec "$JAVACMD" "$@" 187 | -------------------------------------------------------------------------------- /src/test/java/com/example/osgi/framework/tests/FelixServiceTests.java: -------------------------------------------------------------------------------- 1 | package com.example.osgi.framework.tests; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.ActiveProfiles; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | 12 | import com.example.osgi.framework.felix.FelixService; 13 | 14 | @RunWith(SpringRunner.class) 15 | @SpringBootTest 16 | @ActiveProfiles("dev") 17 | public class FelixServiceTests { 18 | 19 | @Autowired 20 | private FelixService fs; 21 | 22 | @Test 23 | public void felixServiceLoads() throws Exception { 24 | assertThat(fs).isNotNull(); 25 | } 26 | 27 | } 28 | --------------------------------------------------------------------------------