diff --git a/spring-integration-smpp/README.md b/spring-integration-smpp/README.md new file mode 100644 index 0000000..1982241 --- /dev/null +++ b/spring-integration-smpp/README.md @@ -0,0 +1,86 @@ +Spring Integration Smpp Adapter +================================================= + +The Spring Integration Smpp allows you to receive/send [Short Message Service][] (SMS) messages to a [Short message service center][] (SMSC) using the [SMPP][] protocol. + +# Components + +* Outbound Channel Adapter +* Outbound Gateway +* Inbound Channel Adapter +* Inbound Gateway + +# Requirements + +For running the tests you're going to need a good server to test with: + +There are 2 options: + +**SMPPSim** - http://www.seleniumsoftware.com/regform.php?itemdesc=SMPPSim.tar.gz + +Simply download it, cd into the folder and execute `./startsmppsim.(sh|bat)`. Make sure the script is executable. The configuration for this simulator is in *conf/smppsim.props* + +Another option is **smsssim** and smsctest from http://opensmpp.logica.com/CommonPart/Download/download2.html + +Alternatively, the JSMPP project itself has an SMPP simulator as well. It is also possible to use a full-blow SMPP servers like *Kanell*. + +# Building + +If you encounter out of memory errors during the build, increase available heap and permgen for Gradle: + + GRADLE_OPTS='-XX:MaxPermSize=1024m -Xmx1024m' + +To build and install jars into your local Maven cache: + + ./gradlew install + +To build api Javadoc (results will be in `build/api`): + + ./gradlew api + +To build reference documentation (results will be in `build/reference`): + + ./gradlew reference + +To build complete distribution including `-dist`, `-docs`, and `-schema` zip files (results will be in `build/distributions`) + + ./gradlew dist + +# Using SpringSource Tool Suite + + Gradle projects can be directly imported into STS + +# Using PLain Eclipse + +To generate Eclipse metadata (.classpath and .project files), do the following: + + ./gradlew eclipse + +Once complete, you may then import the projects into Eclipse as usual: + + *File -> Import -> Existing projects into workspace* + +Browse to the *'spring-integration'* root directory. All projects should import +free of errors. + +# Using IntelliJ IDEA + +To generate IDEA metadata (.iml and .ipr files), do the following: + + ./gradlew idea + +For more information, please visit the Spring Integration website at: +[http://www.springsource.org/spring-integration](http://www.springsource.org/spring-integration) + +# TODO + +* figure out a clean way to furnish our own Executor implementation + +# Resources + +* http://www.techdive.in/java/send-sms-using-jsmpp +* http://www.linkedin.com/answers/technology/information-technology/telecommunications/TCH_ITS_TCI/461130-44316394 + +[SMPP]: http://en.wikipedia.org/wiki/Short_Message_Peer-to-Peer +[Short Message Service]: http://en.wikipedia.org/wiki/Short_Message_Service +[Short message service center]: http://en.wikipedia.org/wiki/Short_message_service_center diff --git a/spring-integration-smpp/build.gradle b/spring-integration-smpp/build.gradle new file mode 100644 index 0000000..7b127e7 --- /dev/null +++ b/spring-integration-smpp/build.gradle @@ -0,0 +1,273 @@ +description = 'Spring Integration Smpp Adapter' + +buildscript { + repositories { + maven { url 'https://repo.springsource.org/plugins-snapshot' } + } + dependencies { + classpath 'org.springframework.build.gradle:docbook-reference-plugin:0.1.5' + } +} + +apply plugin: 'java' +apply from: "${rootProject.projectDir}/publish-maven.gradle" +apply plugin: 'eclipse' +apply plugin: 'idea' + +group = 'org.springframework.integration' + +repositories { + maven { url 'http://repo.springsource.org/libs-milestone' } + maven { url 'http://repo.springsource.org/plugins-release' } +} + +sourceCompatibility=1.6 +targetCompatibility=1.6 + +ext { + jsmppVersion = '2.1.0' + slf4jVersion = '1.6.6' + commonsLangVersion = '2.6' + commonsBeanUtilsVersion= '1.8.3' + easymockVersion = '2.3' + junitVersion = '4.10' + log4jVersion = '1.2.12' + mockitoVersion = '1.9.0' + springVersion = '3.1.3.RELEASE' + springIntegrationVersion = '2.2.0.RELEASE' + + idPrefix = 'smpp' +} + +eclipse { + project { + natures += 'org.springframework.ide.eclipse.core.springnature' + } +} + +sourceSets { + test { + resources { + srcDirs = ['src/test/resources', 'src/test/java'] + } + } +} + +// See http://www.gradle.org/docs/current/userguide/dependency_management.html#sub:configurations +// and http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.ConfigurationContainer.html +configurations { + jacoco //Configuration Group used by Sonar to provide Code Coverage using JaCoCo +} + +dependencies { + compile "com.googlecode.jsmpp:jsmpp:$jsmppVersion" + compile "org.slf4j:slf4j-log4j12:$slf4jVersion" + compile "commons-lang:commons-lang:$commonsLangVersion" + compile "commons-beanutils:commons-beanutils:$commonsBeanUtilsVersion" + compile "org.springframework.integration:spring-integration-core:$springIntegrationVersion" + compile "org.springframework.integration:spring-integration-core:$springIntegrationVersion" + testCompile "org.springframework.integration:spring-integration-test:$springIntegrationVersion" + testCompile "junit:junit-dep:$junitVersion" + testCompile "log4j:log4j:$log4jVersion" + testCompile "org.mockito:mockito-all:$mockitoVersion" + testCompile "org.springframework:spring-test:$springVersion" + testCompile "org.easymock:easymockclassextension:$easymockVersion" + jacoco group: "org.jacoco", name: "org.jacoco.agent", version: "0.5.6.201201232323", classifier: "runtime" +} + +// enable all compiler warnings; individual projects may customize further +ext.xLintArg = '-Xlint:all' +[compileJava, compileTestJava]*.options*.compilerArgs = [xLintArg] + +test { + // suppress all console output during testing unless running `gradle -i` + logging.captureStandardOutput(LogLevel.INFO) + jvmArgs "-javaagent:${configurations.jacoco.asPath}=destfile=${buildDir}/jacoco.exec,includes=*" +} + +task sourcesJar(type: Jar) { + classifier = 'sources' + from sourceSets.main.allJava +} + +task javadocJar(type: Jar) { + classifier = 'javadoc' + from javadoc +} + +artifacts { + archives sourcesJar + archives javadocJar +} + +apply plugin: 'docbook-reference' + +reference { + sourceDir = file('src/reference/docbook') +} + +apply plugin: 'sonar' + +sonar { + + if (rootProject.hasProperty('sonarHostUrl')) { + server.url = rootProject.sonarHostUrl + } + + database { + if (rootProject.hasProperty('sonarJdbcUrl')) { + url = rootProject.sonarJdbcUrl + } + if (rootProject.hasProperty('sonarJdbcDriver')) { + driverClassName = rootProject.sonarJdbcDriver + } + if (rootProject.hasProperty('sonarJdbcUsername')) { + username = rootProject.sonarJdbcUsername + } + if (rootProject.hasProperty('sonarJdbcPassword')) { + password = rootProject.sonarJdbcPassword + } + } + + project { + dynamicAnalysis = "reuseReports" + withProjectProperties { props -> + props["sonar.core.codeCoveragePlugin"] = "jacoco" + props["sonar.jacoco.reportPath"] = "${buildDir.name}/jacoco.exec" + } + } + + logger.info("Sonar parameters used: server.url='${server.url}'; database.url='${database.url}'; database.driverClassName='${database.driverClassName}'; database.username='${database.username}'") +} + +task api(type: Javadoc) { + group = 'Documentation' + description = 'Generates the Javadoc API documentation.' + title = "${rootProject.description} ${version} API" + options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED + options.author = true + options.header = rootProject.description + options.overview = 'src/api/overview.html' + + source = sourceSets.main.allJava + classpath = project.sourceSets.main.compileClasspath + destinationDir = new File(buildDir, "api") +} + +task schemaZip(type: Zip) { + group = 'Distribution' + classifier = 'schema' + description = "Builds -${classifier} archive containing all " + + "XSDs for deployment at static.springframework.org/schema." + + def Properties schemas = new Properties(); + def shortName = idPrefix.replaceFirst("${idPrefix}-", '') + + project.sourceSets.main.resources.find { + it.path.endsWith('META-INF/spring.schemas') + }?.withInputStream { schemas.load(it) } + + for (def key : schemas.keySet()) { + File xsdFile = project.sourceSets.main.resources.find { + it.path.endsWith(schemas.get(key)) + } + assert xsdFile != null + into ("integration/${shortName}") { + from xsdFile.path + } + } +} + +task docsZip(type: Zip) { + group = 'Distribution' + classifier = 'docs' + description = "Builds -${classifier} archive containing api and reference " + + "for deployment at static.springframework.org/spring-integration/docs." + + from('src/dist') { + include 'changelog.txt' + } + + from (api) { + into 'api' + } + + from (reference) { + into 'reference' + } +} + +task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) { + group = 'Distribution' + classifier = 'dist' + description = "Builds -${classifier} archive, containing all jars and docs, " + + "suitable for community download page." + + ext.baseDir = "${project.name}-${project.version}"; + + from('src/dist') { + include 'readme.txt' + include 'license.txt' + include 'notice.txt' + into "${baseDir}" + } + + from(zipTree(docsZip.archivePath)) { + into "${baseDir}/docs" + } + + from(zipTree(schemaZip.archivePath)) { + into "${baseDir}/schema" + } + + into ("${baseDir}/libs") { + from project.jar + from project.sourcesJar + from project.javadocJar + } +} + +// Create an optional "with dependencies" distribution. +// Not published by default; only for use when building from source. +task depsZip(type: Zip, dependsOn: distZip) { zipTask -> + group = 'Distribution' + classifier = 'dist-with-deps' + description = "Builds -${classifier} archive, containing everything " + + "in the -${distZip.classifier} archive plus all dependencies." + + from zipTree(distZip.archivePath) + + gradle.taskGraph.whenReady { taskGraph -> + if (taskGraph.hasTask(":${zipTask.name}")) { + def projectName = rootProject.name + def artifacts = new HashSet() + + rootProject.configurations.runtime.resolvedConfiguration.resolvedArtifacts.each { artifact -> + def dependency = artifact.moduleVersion.id + if (!projectName.equals(dependency.name)) { + artifacts << artifact.file + } + } + + zipTask.from(artifacts) { + into "${distZip.baseDir}/deps" + } + } + } +} + +artifacts { + archives distZip + archives docsZip + archives schemaZip +} + +task dist(dependsOn: assemble) { + group = 'Distribution' + description = 'Builds -dist, -docs and -schema distribution archives.' +} + +task wrapper(type: Wrapper) { + description = 'Generates gradlew[.bat] scripts' + gradleVersion = '1.3' +} diff --git a/spring-integration-smpp/gradle.properties b/spring-integration-smpp/gradle.properties new file mode 100644 index 0000000..f212918 --- /dev/null +++ b/spring-integration-smpp/gradle.properties @@ -0,0 +1 @@ +version=2.2.0.BUILD-SNAPSHOT diff --git a/spring-integration-smpp/gradle/wrapper/gradle-wrapper.jar b/spring-integration-smpp/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7b359d7 Binary files /dev/null and b/spring-integration-smpp/gradle/wrapper/gradle-wrapper.jar differ diff --git a/spring-integration-smpp/gradle/wrapper/gradle-wrapper.properties b/spring-integration-smpp/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..6ebbb7b --- /dev/null +++ b/spring-integration-smpp/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Jan 08 17:13:07 EST 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http\://services.gradle.org/distributions/gradle-1.3-bin.zip diff --git a/spring-integration-smpp/gradlew b/spring-integration-smpp/gradlew new file mode 100755 index 0000000..3851082 --- /dev/null +++ b/spring-integration-smpp/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" +APP_HOME="`pwd -P`" +cd "$SAVED" + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/spring-integration-smpp/gradlew.bat b/spring-integration-smpp/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/spring-integration-smpp/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/spring-integration-smpp/publish-maven.gradle b/spring-integration-smpp/publish-maven.gradle new file mode 100644 index 0000000..1f053ad --- /dev/null +++ b/spring-integration-smpp/publish-maven.gradle @@ -0,0 +1,61 @@ +apply plugin: 'maven' + +ext.optionalDeps = [] +ext.providedDeps = [] + +ext.optional = { optionalDeps << it } +ext.provided = { providedDeps << it } + +install { + repositories.mavenInstaller { + customizePom(pom, project) + } +} + +def customizePom(pom, gradleProject) { + pom.whenConfigured { generatedPom -> + // respect 'optional' and 'provided' dependencies + gradleProject.optionalDeps.each { dep -> + generatedPom.dependencies.find { it.artifactId == dep.name }?.optional = true + } + gradleProject.providedDeps.each { dep -> + generatedPom.dependencies.find { it.artifactId == dep.name }?.scope = 'provided' + } + + // eliminate test-scoped dependencies (no need in maven central poms) + generatedPom.dependencies.removeAll { dep -> + dep.scope == 'test' + } + + // add all items necessary for maven central publication + generatedPom.project { + name = gradleProject.description + description = gradleProject.description + url = 'https://github.com/SpringSource/spring-integration' + organization { + name = 'SpringSource' + url = 'http://springsource.org' + } + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + scm { + url = 'https://github.com/SpringSource/spring-integration' + connection = 'scm:git:git://github.com/SpringSource/spring-integration' + developerConnection = 'scm:git:git://github.com/SpringSource/spring-integration' + } + + developers { + developer { + id = 'not specified' + name = 'Johanes Soetanto' + email = 'not specified' + } + } + } + } +} diff --git a/spring-integration-smpp/src/api/overview.html b/spring-integration-smpp/src/api/overview.html new file mode 100644 index 0000000..e2c6bf5 --- /dev/null +++ b/spring-integration-smpp/src/api/overview.html @@ -0,0 +1,22 @@ + + +This document is the API specification for Spring Integration Smpp Module +
+
+

+ For further API reference and developer documentation, see the + Spring + Integration reference documentation. + That documentation contains more detailed, developer-targeted + descriptions, with conceptual overviews, definitions of terms, + workarounds, and working code examples. +

+ +

+ If you are interested in commercial training, consultancy, and + support for Spring Integration, please visit + http://www.springsource.com +

+
+ + diff --git a/spring-integration-smpp/src/dist/changelog.txt b/spring-integration-smpp/src/dist/changelog.txt new file mode 100644 index 0000000..39bddd0 --- /dev/null +++ b/spring-integration-smpp/src/dist/changelog.txt @@ -0,0 +1,12 @@ +Spring Integration Smpp Adapter CHANGELOG +========================================= + +Feature in version 2.2.0 + +1. Inbound channel adapter + +2. Outbound channel adapter + +3. Inbound gateway + +4. Outbound gateway \ No newline at end of file diff --git a/spring-integration-smpp/src/dist/license.txt b/spring-integration-smpp/src/dist/license.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/spring-integration-smpp/src/dist/license.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/spring-integration-smpp/src/dist/notice.txt b/spring-integration-smpp/src/dist/notice.txt new file mode 100644 index 0000000..f62045a --- /dev/null +++ b/spring-integration-smpp/src/dist/notice.txt @@ -0,0 +1,21 @@ + ======================================================================== + == NOTICE file corresponding to section 4 d of the Apache License, == + == Version 2.0, in this case for the Spring Integration distribution. == + ======================================================================== + + This product includes software developed by + the Apache Software Foundation (http://www.apache.org). + + The end-user documentation included with a redistribution, if any, + must include the following acknowledgement: + + "This product includes software developed by the Spring Framework + Project (http://www.springframework.org)." + + Alternatively, this acknowledgement may appear in the software itself, + if and wherever such third-party acknowledgements normally appear. + + The names "Spring", "Spring Framework", and "Spring Integration" must + not be used to endorse or promote products derived from this software + without prior written permission. For written permission, please contact + enquiries@springsource.com. diff --git a/spring-integration-smpp/src/dist/readme.txt b/spring-integration-smpp/src/dist/readme.txt new file mode 100644 index 0000000..fb6fce8 --- /dev/null +++ b/spring-integration-smpp/src/dist/readme.txt @@ -0,0 +1,13 @@ +Spring Integration Smpp Adapter +----------------------------------- + +To find out what has changed since any earlier releases, see 'changelog.txt'. + +Please consult the documentation located within the 'docs/reference' directory +of this release and also visit the official Spring Integration home at +http://www.springsource.org/spring-integration + +There you will find links to the forum, issue tracker, and several other resources. + +See https://github.com/SpringSource/spring-integration#readme for additional +information including instructions on building from source. diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParser.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParser.java new file mode 100644 index 0000000..e5c4f29 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParser.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractChannelAdapterParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.smpp.inbound.SmppInboundChannelAdapter; +import org.w3c.dom.Element; + +/** + * The Smpp Inbound Channel adapter parser + * + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppInboundChannelAdapterParser extends AbstractChannelAdapterParser { + + @Override + protected boolean shouldGenerateId() { + return false; + } + + @Override + protected boolean shouldGenerateIdAsFallback() { + return true; + } + + @Override + protected AbstractBeanDefinition doParse(Element e, ParserContext context, String channelName) { + final BeanDefinitionBuilder builder = BeanDefinitionBuilder + .genericBeanDefinition(SmppInboundChannelAdapter.class); + SmppParserUtils.setSession(e, "smpp-session-ref", "session", "smppSession", context, builder); + builder.addPropertyReference("channel", channelName); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "auto-startup","autoStartup"); + return builder.getBeanDefinition(); + } + +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParser.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParser.java new file mode 100644 index 0000000..3683e6f --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParser.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractInboundGatewayParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.smpp.inbound.SmppInboundGateway; +import org.w3c.dom.Element; + +/** + * The Parser for Smpp Inbound Gateway. + * + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppInboundGatewayParser extends AbstractInboundGatewayParser { + + @Override + protected Class getBeanClass(Element element) { + return SmppInboundGateway.class; + } + + @Override + protected boolean isEligibleAttribute(String n) { + return !n.equals("source-address") && !n.equals("source-ton") && !n.equals("smpp-session-ref") + && !n.equals("request-mapper") && !n.equals("reply-mapper") + && super.isEligibleAttribute(n); + } + + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + super.doParse(element, parserContext, builder); + // because session need parserContext + SmppParserUtils.setSession(element, "smpp-session-ref", "session", "smppSession", parserContext, builder); + } + + @Override + protected void doPostProcess(BeanDefinitionBuilder builder, Element e) { + // value + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "source-address", "defaultSourceAddress"); + SmppParserUtils.setTon(e, "source-ton", "defaultSourceAddressTypeOfNumber", builder); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "reply-timeout", "replyTimeout"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "request-timeout", "requestTimeout"); + + // reference + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, e, "request-mapper", "requestMapper"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, e, "reply-mapper", "replyMapper"); + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppNamespaceHandler.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppNamespaceHandler.java new file mode 100644 index 0000000..4a0d3d3 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppNamespaceHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; + +/** + * The namespace handler for the Smpp namespace + * + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppNamespaceHandler extends AbstractIntegrationNamespaceHandler { + + /* (non-Javadoc) + * @see org.springframework.beans.factory.xml.NamespaceHandler#init() + */ + public void init() { + this.registerBeanDefinitionParser("inbound-channel-adapter", new SmppInboundChannelAdapterParser()); + this.registerBeanDefinitionParser("outbound-channel-adapter", new SmppOutboundChannelAdapterParser()); + this.registerBeanDefinitionParser("inbound-gateway", new SmppInboundGatewayParser()); + this.registerBeanDefinitionParser("outbound-gateway", new SmppOutboundGatewayParser()); + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParser.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParser.java new file mode 100644 index 0000000..143a9a4 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParser.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.smpp.outbound.SmppOutboundChannelAdapter; +import org.w3c.dom.Element; + +/** + * The parser for the Smpp Outbound Channel Adapter. + * + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { + + @Override + protected boolean shouldGenerateIdAsFallback() { + return true; + } + + @Override + protected AbstractBeanDefinition parseConsumer(Element e, ParserContext parserContext) { + final BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SmppOutboundChannelAdapter.class); + // value attributes + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "source-address", "defaultSourceAddress"); + SmppParserUtils.setTon(e, "source-ton", "defaultSourceAddressTypeOfNumber", builder); + // reference attributes + SmppParserUtils.setSession(e, "smpp-session-ref", "session", "smppSession", parserContext, builder); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, e, "time-formatter", "timeFormatter"); + return builder.getBeanDefinition(); + } + +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParser.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParser.java new file mode 100644 index 0000000..4450650 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParser.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractConsumerEndpointParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.smpp.outbound.SmppOutboundGateway; +import org.w3c.dom.Element; + +/** + * The Parser for Smpp Outbound Gateway. + * + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppOutboundGatewayParser extends AbstractConsumerEndpointParser { + @Override + protected BeanDefinitionBuilder parseHandler(Element e, ParserContext parserContext) { + final BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SmppOutboundGateway.class); + // value attributes + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "source-address", "defaultSourceAddress"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "reply-timeout", "sendTimeout"); + SmppParserUtils.setTon(e, "source-ton", "defaultSourceAddressTypeOfNumber", builder); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "order"); + // reference attributes + SmppParserUtils.setSession(e, "smpp-session-ref", "session", "smppSession", parserContext, builder); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, e, "reply-channel", "outputChannel"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, e, "time-formatter", "timeFormatter"); + return builder; + } + + @Override + protected String getInputChannelAttributeName() { + return "request-channel"; + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppParserUtils.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppParserUtils.java new file mode 100644 index 0000000..4a877ec --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppParserUtils.java @@ -0,0 +1,133 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.jsmpp.SMPPConstant; +import org.jsmpp.bean.BindType; +import org.jsmpp.bean.NumberingPlanIndicator; +import org.jsmpp.bean.TypeOfNumber; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +/** + * Contains various utility methods for parsing Smpp Adapter + * specific namespace elements as well as for the generation of the the + * respective {@link BeanDefinition}s. + * + * @author Johanes Soetanto + * @since 2.2 + * + */ +public final class SmppParserUtils { + + /** Prevent instantiation. */ + private SmppParserUtils() { + throw new AssertionError(); + } + + static void setSession(Element e, String sessionAttribute, String sessionChild, String propName, + ParserContext context, BeanDefinitionBuilder builder) { + final String sessionRef = e.getAttribute(sessionAttribute); + final Element sessionElement = DomUtils.getChildElementByTagName(e, sessionChild); + if(StringUtils.hasText(sessionRef)) { + if(sessionElement != null) { + context.getReaderContext().error("Child element '"+sessionChild+"' is not allowed when attribute '" + +sessionAttribute+"' has been specified", context.extractSource(sessionElement)); + } + builder.addPropertyReference(propName, sessionRef); + } else if(sessionElement != null) { + final String ref = sessionElement.getAttribute("ref"); + // + BeanComponentDefinition innerBean = IntegrationNamespaceUtils + .parseInnerHandlerDefinition(sessionElement, context); + if (StringUtils.hasText(ref)) { + builder.addPropertyReference(propName, ref); + } else if (innerBean != null) { + builder.addPropertyValue(propName, innerBean); + } + } else { + context.getReaderContext().error("Child element '"+sessionChild+"' or '"+sessionAttribute+"' attribute " + + "need to be specified", context.extractSource(e)); + } + } + + static void setTon(Element e, String tonAttribute, String propName, BeanDefinitionBuilder builder) { + final String ton = e.getAttribute(tonAttribute); + if (StringUtils.hasText(ton)) { + final RootBeanDefinition tonDef = new RootBeanDefinition(TypeOfNumber.class); + tonDef.setUniqueFactoryMethodName("valueOf"); + tonDef.getConstructorArgumentValues().addGenericArgumentValue(getByteTon(ton)); + builder.addPropertyValue(propName, tonDef); + } + } + + static void setNpi(Element e, String npiAttribute, String propName, BeanDefinitionBuilder builder) { + final String npi = e.getAttribute(npiAttribute); + if (StringUtils.hasText(npi)) { + final RootBeanDefinition npiDef = new RootBeanDefinition(NumberingPlanIndicator.class); + npiDef.setUniqueFactoryMethodName("valueOf"); + npiDef.getConstructorArgumentValues().addGenericArgumentValue(getByteNpi(npi)); + builder.addPropertyValue(propName, npiDef); + } + } + + static void setBindType(Element e, String bindAttribute, String propName, BeanDefinitionBuilder builder) { + final String bt = e.getAttribute(bindAttribute); + if (StringUtils.hasText(bt)) { + final RootBeanDefinition bindTypeDef = new RootBeanDefinition(BindType.class); + bindTypeDef.setUniqueFactoryMethodName("valueOf"); + bindTypeDef.getConstructorArgumentValues().addGenericArgumentValue(getByteBind(bt)); + builder.addPropertyValue(propName, bindTypeDef); + } + } + + private static byte getByteTon(String t) { + if ("ABBREVIATED".equals(t)) return SMPPConstant.TON_ABBREVIATED; + if ("ALPHANUMERIC".equals(t)) return SMPPConstant.TON_ALPHANUMERIC; + if ("SUBSCRIBER_NUMBER".equals(t))return SMPPConstant.TON_SUBSCRIBER_NUMBER; + if ("NETWORK_SPECIFIC".equals(t))return SMPPConstant.TON_NETWORK_SPECIFIC; + if ("NATIONAL".equals(t)) return SMPPConstant.TON_NATIONAL; + if ("INTERNATIONAL".equals(t))return SMPPConstant.TON_INTERNATIONAL; + return SMPPConstant.TON_UNKNOWN; + } + + private static byte getByteNpi(String n) { + if ("WAP".equals(n)) return SMPPConstant.NPI_WAP; + if ("INTERNET".equals(n)) return SMPPConstant.NPI_INTERNET; + if ("ERMES".equals(n)) return SMPPConstant.NPI_ERMES; + if ("PRIVATE".equals(n)) return SMPPConstant.NPI_PRIVATE; + if ("NATIONAL".equals(n)) return SMPPConstant.NPI_NATIONAL; + if ("LAND_MOBILE".equals(n)) return SMPPConstant.NPI_LAND_MOBILE; + if ("TELEX".equals(n)) return SMPPConstant.NPI_TELEX; + if ("DATA".equals(n)) return SMPPConstant.NPI_DATA; + if ("ISDN".equals(n)) return SMPPConstant.NPI_ISDN; + return SMPPConstant.NPI_UNKNOWN; + } + + private static byte getByteBind(String b) { + if ("BIND_RX".equals(b)) return SMPPConstant.CID_BIND_RECEIVER; + if ("BIND_TX".equals(b)) return SMPPConstant.CID_BIND_TRANSMITTER; + return SMPPConstant.CID_BIND_TRANSCEIVER; + } + +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/package-info.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/package-info.java new file mode 100644 index 0000000..b06f273 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides parser classes to provide Xml namespace support for the Smpp components. + */ +package org.springframework.integration.smpp.config.xml; diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/AbstractReceivingMessageListener.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/AbstractReceivingMessageListener.java new file mode 100644 index 0000000..02355f0 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/AbstractReceivingMessageListener.java @@ -0,0 +1,61 @@ +package org.springframework.integration.smpp.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.bean.*; +import org.jsmpp.extra.ProcessRequestException; +import org.jsmpp.session.DataSmResult; +import org.jsmpp.session.MessageReceiverListener; +import org.jsmpp.session.Session; + +/** + * @author Josh Long + * @since 2.1 + */ +abstract public class AbstractReceivingMessageListener implements MessageReceiverListener { + + private Log logger = LogFactory.getLog(getClass()); + + public void onAcceptDeliverSm(DeliverSm deliverSm) throws ProcessRequestException { + if (MessageType.SMSC_DEL_RECEIPT.containedIn(deliverSm.getEsmClass())) { // delivery receipt + try { + DeliveryReceipt delReceipt = deliverSm.getShortMessageAsDeliveryReceipt(); + long id = Long.parseLong(delReceipt.getId()); + String messageId = Long.toString(id, 16).toUpperCase(); + onDeliveryReceipt(deliverSm, messageId, delReceipt); + logger.debug("Receiving delivery receipt for message '" + messageId + "' : " + delReceipt); + } catch (Exception e) { + logger.error("Failed getting delivery receipt", e); + throw new RuntimeException(e); + } + } else { + try {// this is an actual SMS message + byte[] shortMessage = deliverSm.getShortMessage(); + String txtSms = shortMessage == null ? new String() : new String(shortMessage); + logger.debug("Receiving message : " + txtSms); + onTextMessage(deliverSm, txtSms); + } catch (Exception e) { + logger.error("Failed getting short message", e); + throw new RuntimeException(e); + } + } + } + + public void onAcceptAlertNotification(AlertNotification alertNotification) { + + } + + public DataSmResult onAcceptDataSm(DataSm dataSm, Session source) throws ProcessRequestException { + return null; + } + + /** + * specific callback for a receipt, which you'll only get if the outbound message had a specific delivery receipt setting. + */ + abstract protected void onDeliveryReceipt(DeliverSm deliverSm, String ogMessageId, DeliveryReceipt deliveryReceipt) throws Exception; + + /** + * specific callback for proper SMS, text-based messages. + */ + abstract protected void onTextMessage(DeliverSm deliverSm, String txtMessage) throws Exception; +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmesMessageSpecification.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmesMessageSpecification.java new file mode 100644 index 0000000..5d1ee2a --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmesMessageSpecification.java @@ -0,0 +1,574 @@ +package org.springframework.integration.smpp.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.bean.*; +import org.jsmpp.session.ClientSession; +import org.jsmpp.session.SMPPSession; +import org.jsmpp.util.AbsoluteTimeFormatter; +import org.jsmpp.util.TimeFormatter; +import org.springframework.integration.Message; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.util.Date; + +import static org.springframework.integration.smpp.core.SmppConstants.*; + +/** + * fluent API to help make specifying all these parameters just a tiny bit easier. For internal use only. + * + * @author Josh Long + * @since 2.1 + */ +public class SmesMessageSpecification { + + private Log log = LogFactory.getLog(getClass()); + private TimeFormatter timeFormatter = new AbsoluteTimeFormatter(); + + private int maxLengthSmsMessages = 140; + private String sourceAddress; + private String destinationAddress; + private String serviceType; + private TypeOfNumber sourceAddressTypeOfNumber; + private NumberingPlanIndicator sourceAddressNumberingPlanIndicator; + private TypeOfNumber destinationAddressTypeOfNumber; + private NumberingPlanIndicator destinationAddressNumberingPlanIndicator; + private ESMClass esmClass; + private byte protocolId; + private byte priorityFlag; + private String scheduleDeliveryTime = timeFormatter.format(new Date()); + private String validityPeriod; + private RegisteredDelivery registeredDelivery; + private byte replaceIfPresentFlag; + private DataCoding dataCoding; + private byte smDefaultMsgId; + private byte[] shortMessage; + private ClientSession smppSession; + + /** + * this method takes an inbound SMS message and converts it to a Spring Integration message + * + * @param dsm the {@link DeliverSm} from {@link AbstractReceivingMessageListener#onTextMessage(org.jsmpp.bean.DeliverSm, String)} + * @param txtMessage the String from {@link AbstractReceivingMessageListener#onTextMessage(org.jsmpp.bean.DeliverSm, String)} + * @return a Spring Integration message + */ + public static Message toMessageFromSms(DeliverSm dsm, String txtMessage) { + + Assert.isTrue(!dsm.isSmscDeliveryReceipt(), "the message should not be a delivery confirmation receipt!"); + + MessageBuilder mb = MessageBuilder.withPayload(txtMessage); + mb.setHeader(SmppConstants.SMS, dsm); + mb.setHeader(SmppConstants.REPLACE_IF_PRESENT, dsm.getReplaceIfPresent()); + mb.setHeader(SmppConstants.SHORT_MESSAGE, dsm.getShortMessage()); + mb.setHeader(SmppConstants.OPTIONAL_PARAMETERS, dsm.getOptionalParameters()); + mb.setHeader(SmppConstants.UDHI_AND_REPLY_PATH, dsm.isUdhiAndReplyPath()); + mb.setHeader(SmppConstants.VALIDITY_PERIOD, dsm.getValidityPeriod()); + mb.setHeader(SmppConstants.COMMAND_LENGTH, dsm.getCommandLength()); + mb.setHeader(SmppConstants.COMMAND_ID, dsm.getCommandId()); + mb.setHeader(SmppConstants.SME_ACK_NOT_REQUESTED, dsm.isSmeAckNotRequested()); + mb.setHeader(SmppConstants.DATA_CODING, dsm.getDataCoding()); + mb.setHeader(SmppConstants.REPLY_PATH, dsm.isReplyPath()); + mb.setHeader(SmppConstants.SOURCE_ADDR_TON, dsm.getSourceAddrTon()); + mb.setHeader(SmppConstants.SM_DEFAULT_MSG_ID, dsm.getSmDefaultMsgId()); + mb.setHeader(SmppConstants.UDHI, dsm.isUdhi()); + mb.setHeader(SmppConstants.SME_MANUAL_ACKNOWLEDGMENT, dsm.isSmeManualAcknowledgment()); + mb.setHeader(SmppConstants.CONVERSATION_ABORT, dsm.isConversationAbort()); + mb.setHeader(SmppConstants.DEST_ADDRESS, dsm.getDestAddress()); + mb.setHeader(SmppConstants.ESM_CLASS, dsm.getEsmClass()); + mb.setHeader(SmppConstants.COMMAND_ID_AS_HEX, dsm.getCommandIdAsHex()); + mb.setHeader(SmppConstants.SME_DELIVERY_AND_MANUAL_ACK_REQUESTED, dsm.isSmeDeliveryAndManualAckRequested()); + mb.setHeader(SmppConstants.SMSC_DELIVERY_RECEIPT, dsm.isSmscDeliveryReceipt()); + mb.setHeader(SmppConstants.SME_MANUAL_ACK_REQUESTED, dsm.isSmeManualAckRequested()); + mb.setHeader(SmppConstants.PRIORITY_FLAG, dsm.getPriorityFlag()); + mb.setHeader(SmppConstants.DEST_ADDR_TON, dsm.getDestAddrTon()); + mb.setHeader(SmppConstants.COMMAND_STATUS_AS_HEX, dsm.getCommandStatusAsHex()); + mb.setHeader(SmppConstants.SERVICE_TYPE, dsm.getServiceType()); + mb.setHeader(SmppConstants.INTERMEDIATE_DELIVERY_NOTIFICATION, dsm.isIntermedietDeliveryNotification()); + mb.setHeader(SmppConstants.SOURCE_ADDR_NPI, dsm.getSourceAddrNpi()); + mb.setHeader(SmppConstants.REGISTERED_DELIVERY, dsm.getRegisteredDelivery()); + mb.setHeader(SmppConstants.DEST_ADDR_NPI, dsm.getDestAddrNpi()); + mb.setHeader(SmppConstants.COMMAND_STATUS, dsm.getCommandStatus()); + mb.setHeader(SmppConstants.DEFAULT_MESSAGE_TYPE, dsm.isDefaultMessageType()); + mb.setHeader(SmppConstants.PROTOCOL_ID, dsm.getProtocolId()); + mb.setHeader(SmppConstants.SOURCE_ADDR, dsm.getSourceAddr()); + mb.setHeader(SmppConstants.SEQUENCE_NUMBER, dsm.getSequenceNumber()); + mb.setHeader(SmppConstants.SCHEDULE_DELIVERY_TIME, dsm.getScheduleDeliveryTime()); + mb.setHeader(SmppConstants.SME_DELIVERY_ACK_REQUESTED, dsm.isSmeDeliveryAckRequested()); + return mb.build(); + } + + /** + * this method will take an inbound Spring Integration {@link Message} and map it to a {@link SmesMessageSpecification} + * which we can use to send the SMS message. + * + * @param msg a new {@link Message} + * @param smppSession the SMPPSession + * @return a {@link SmesMessageSpecification} + */ + public static SmesMessageSpecification fromMessage(ClientSession smppSession, Message msg) { + System.out.println("Message: "+msg); + String srcAddy = valueIfHeaderExists(SRC_ADDR, msg); + String dstAddy = valueIfHeaderExists(DST_ADDR, msg); + String smsTxt = valueIfHeaderExists(SMS_MSG, msg); + if (!StringUtils.hasText(smsTxt)) { + Object payload = msg.getPayload(); + if (payload instanceof String) { + smsTxt = (String) payload; + } + } + SmesMessageSpecification spec = SmesMessageSpecification.newSmesMessageSpecification(smppSession, srcAddy, dstAddy, smsTxt); + spec.setDestinationAddressNumberingPlanIndicator(SmesMessageSpecification.valueIfHeaderExists(DST_NPI, msg)); + spec.setSourceAddressNumberingPlanIndicator(SmesMessageSpecification.valueIfHeaderExists(SRC_NPI, msg)); + spec.setDestinationAddressTypeOfNumber(SmesMessageSpecification.valueIfHeaderExists(DST_TON, msg)); + spec.setSourceAddressTypeOfNumber(SmesMessageSpecification.valueIfHeaderExists(SRC_TON, msg)); + spec.setServiceType(SmesMessageSpecification.valueIfHeaderExists(SERVICE_TYPE, msg)); + spec.setEsmClass(SmesMessageSpecification.esmClassFromHeader(msg)); + spec.setScheduleDeliveryTime(SmesMessageSpecification.valueIfHeaderExists(SCHEDULED_DELIVERY_TIME, msg)); + spec.setDataCoding(SmesMessageSpecification. dataCodingFromHeader( msg)); + spec.setValidityPeriod(SmesMessageSpecification.valueIfHeaderExists(VALIDITY_PERIOD, msg)); + + // byte landmine. autoboxing causes havoc with null bytes. + Byte priorityFlag1 = SmesMessageSpecification.valueIfHeaderExists(PRIORITY_FLAG, msg); + if (priorityFlag1 != null) + spec.setPriorityFlag(priorityFlag1); + + Byte smDefaultMsgId1 = SmesMessageSpecification.valueIfHeaderExists(SM_DEFAULT_MSG_ID, msg); + if (smDefaultMsgId1 != null) + spec.setSmDefaultMsgId(smDefaultMsgId1); + + Byte replaceIfPresentFlag1 = SmesMessageSpecification.valueIfHeaderExists(REPLACE_IF_PRESENT_FLAG, msg); + if (replaceIfPresentFlag1 != null) + spec.setReplaceIfPresentFlag(replaceIfPresentFlag1); + + Byte protocolId1 = SmesMessageSpecification.valueIfHeaderExists(PROTOCOL_ID, msg); + if (null != protocolId1) + spec.setProtocolId(protocolId1); + + spec.setRegisteredDelivery(registeredDeliveryFromHeader(msg)); + + return spec; + } + + private static DataCoding dataCodingFromHeader( Message msg) { + Object dc = msg.getHeaders().get(DATA_CODING); + if(dc instanceof DataCoding){ + return (DataCoding)dc ; + } + if( dc instanceof Byte){ + return DataCodings.newInstance((Byte)dc); + } + + return null ; + } + + /** + * need to be a little flexibile about what we take in as {@link SmppConstants#REGISTERED_DELIVERY_MODE}. The value can + * be a String or a member of the {@link SMSCDeliveryReceipt} enum. + * + * @param msg the Spring Integration message + * @return a value for {@link RegisteredDelivery} or null, which is good because it'll simply let the existing default work + */ + private static RegisteredDelivery registeredDeliveryFromHeader(Message msg) { + Object rd = valueIfHeaderExists(REGISTERED_DELIVERY_MODE, msg); + + if (rd instanceof String) { + String rdString = (String) rd; + SMSCDeliveryReceipt smscDeliveryReceipt = SMSCDeliveryReceipt.valueOf(rdString); + Assert.notNull(smscDeliveryReceipt, "the registeredDelivery can't be null"); + return new RegisteredDelivery(smscDeliveryReceipt); + } + + if (rd instanceof SMSCDeliveryReceipt) { + SMSCDeliveryReceipt smscDeliveryReceipt = (SMSCDeliveryReceipt) rd; + return new RegisteredDelivery(smscDeliveryReceipt); + } + + if (rd instanceof RegisteredDelivery) { + return (RegisteredDelivery) rd; + } + return null; + } + + /** + * you need to use the builder API + * + * @param smppSession the SMPPSession instance against which we should work. + * @see SmesMessageSpecification#SmesMessageSpecification() + */ + SmesMessageSpecification(SMPPSession smppSession) { + this.smppSession = smppSession; + } + + /** + * tries to safely extract the ESMClass + * @param im + * @return + */ + static private ESMClass esmClassFromHeader( Message im){ + String h = ESM_CLASS ; + Object o = valueIfHeaderExists(h,im); + ESMClass response = null ; + if(o instanceof Byte){ + response = new ESMClass((Byte)o); + + } + else if(o instanceof ESMClass){ + response = (ESMClass)o; + } + return response; + } + + @SuppressWarnings("unchecked") + static private T valueIfHeaderExists(String h, Message msg) { + if (msg != null && msg.getHeaders().containsKey(h)) + return (T) msg.getHeaders().get(h); + return null; + } + + /** + * Everybody else has to use the builder API. DO NOT make this private or it will not be proxied and that will make me sad! + * + * @param ss the {@link SMPPSession} + * @return the current spec + */ + SmesMessageSpecification setSmppSession(ClientSession ss) { + this.smppSession = ss; + return this; + } + + /** + * use the builder API, but we need this to cleanly proxy + */ + SmesMessageSpecification() { + this(null); + } + + /** + * Conceptually, you could get away with just specifying these three parameters, though I don't know how likely that is in practice. + * + * @param srcAddress the source address + * @param destAddress the destination address + * @param txtMessage the message to send (must be no more than 140 characters + * @param ss the SMPPSession + * @return the {@link SmesMessageSpecification} + */ + public static SmesMessageSpecification newSmesMessageSpecification(ClientSession ss, String srcAddress, String destAddress, String txtMessage) { + + SmesMessageSpecification smesMessageSpecification = new SmesMessageSpecification(); + + smesMessageSpecification + .reset() + .setSmppSession(ss) + .setSourceAddress(srcAddress) + .setDestinationAddress(destAddress) + .setShortTextMessage(txtMessage); + + return smesMessageSpecification; + } + + /** + * Only sets the #sourceAddressTypeOfNumber if the current value is null, otherwise, it leaves it. + * + * @param sourceAddressTypeOfNumberIfRequired + * the {@link TypeOfNumber} + * @return this + */ + public SmesMessageSpecification setSourceAddressTypeOfNumberIfRequired(TypeOfNumber sourceAddressTypeOfNumberIfRequired) { + if (this.sourceAddressTypeOfNumber == null) + this.sourceAddressTypeOfNumber = sourceAddressTypeOfNumberIfRequired; + return this; + } + + /** + * send the message on its way. + *

+ * todo can we do something smart here or through an adapter to handle the situation where we have asked for a message receipt? what about if we're using a message receipt and we're only a receiver or a sender connection and not a transceiver? We need gateway semantics across two unidirectional SMPPSessions, then + * + * @return the messageId (required if you want to then track it or correllate it with message receipt confirmations) + * @throws Exception the {@link SMPPSession#submitShortMessage(String, org.jsmpp.bean.TypeOfNumber, org.jsmpp.bean.NumberingPlanIndicator, String, org.jsmpp.bean.TypeOfNumber, org.jsmpp.bean.NumberingPlanIndicator, String, org.jsmpp.bean.ESMClass, byte, byte, String, String, org.jsmpp.bean.RegisteredDelivery, byte, org.jsmpp.bean.DataCoding, byte, byte[], org.jsmpp.bean.OptionalParameter...)} method throws lots of Exceptions, including {@link java.io.IOException} + */ + public String send() throws Exception { + validate(); + String msgId = this.smppSession.submitShortMessage( + this.serviceType, + this.sourceAddressTypeOfNumber, + this.sourceAddressNumberingPlanIndicator, + this.sourceAddress, + + this.destinationAddressTypeOfNumber, + this.destinationAddressNumberingPlanIndicator, + this.destinationAddress, + + this.esmClass, + this.protocolId, + this.priorityFlag, + this.scheduleDeliveryTime, + this.validityPeriod, + this.registeredDelivery, + this.replaceIfPresentFlag, + this.dataCoding, + this.smDefaultMsgId, + this.shortMessage); + + return msgId; + } + + protected void validate() { + Assert.notNull(this.sourceAddress, "the source address must not be null"); + Assert.notNull(this.destinationAddress, "the destination address must not be null"); + Assert.isTrue(this.shortMessage != null && this.shortMessage.length > 0, "the message must not be null"); + } + + public SmesMessageSpecification setSourceAddress(String sourceAddr) { + if (!nullHeaderWillOverwriteDefault(sourceAddr)) + this.sourceAddress = sourceAddr; + return this; + } + + /** + * the 'to' phone number + * + * @param destinationAddr the phone number + * @return the current spec + */ + public SmesMessageSpecification setDestinationAddress(String destinationAddr) { + this.destinationAddress = destinationAddr; + return this; + } + + public SmesMessageSpecification setServiceType(String serviceType) { + if (!nullHeaderWillOverwriteDefault(serviceType)) + this.serviceType = serviceType; + return this; + } + + public SmesMessageSpecification setSourceAddressTypeOfNumber(TypeOfNumber sourceAddrTon) { + if (!nullHeaderWillOverwriteDefault(sourceAddrTon)) + this.sourceAddressTypeOfNumber = sourceAddrTon; + return this; + } + + public SmesMessageSpecification setSourceAddressNumberingPlanIndicator(NumberingPlanIndicator sourceAddrNpi) { + if (!nullHeaderWillOverwriteDefault(sourceAddrNpi)) + this.sourceAddressNumberingPlanIndicator = sourceAddrNpi; + return this; + } + + public SmesMessageSpecification setDestinationAddressTypeOfNumber(TypeOfNumber destAddrTon) { + if (!nullHeaderWillOverwriteDefault(destAddrTon)) + this.destinationAddressTypeOfNumber = destAddrTon; + return this; + } + + /** + * guard against overwriting perfectly good defaults with null values. + * + * @param v value the value + * @return can the write proceed unabated? + */ + private boolean nullHeaderWillOverwriteDefault(Object v) { + if (v == null) { + if (log.isDebugEnabled()) log.debug("There is a default in place for this property; don't overwrite it with null"); + return true; + } + return false; + } + + public SmesMessageSpecification setDestinationAddressNumberingPlanIndicator(NumberingPlanIndicator destAddrNpi) { + if (!nullHeaderWillOverwriteDefault(destAddrNpi)) + this.destinationAddressNumberingPlanIndicator = destAddrNpi; + return this; + } + + public SmesMessageSpecification setEsmClass(ESMClass esmClass) { + if (!nullHeaderWillOverwriteDefault(esmClass)) + this.esmClass = esmClass; + return this; + } + + public SmesMessageSpecification setProtocolId(byte protocolId) { + if (!nullHeaderWillOverwriteDefault(protocolId)) + this.protocolId = protocolId; + return this; + } + + public SmesMessageSpecification setPriorityFlag(byte pf) { + if (!nullHeaderWillOverwriteDefault(pf)) + this.priorityFlag = pf; + return this; + } + + /** + * When you submit a message to an SMSC, it is possible to sometimes specify a + * validity period for the message. This setting is an instruction to the SMSC that stipulates that + * if the message cannot be delivered to the recipient within the next N minutes or hours or days, + * the SMSC should discard the message. This would mean that if the recipient'running mobile phone is + * turned off, or outSession of coverage for x minutes/hours/days after the message is submitted, the SMSC + * should not perform further delivery retry and should discard the message. + *

+ * Of course, there is no guarantee that the operator SMSC will respect this setting, so it needs + * to be tested with a particular operator first to determine if it can be used reliably. + *

+ * That information came from the NowSMS website.. + * + * @param v the period of validity. There are specific formats for this, however this method provides no validation. + *

+ * todo provide format validation if possible + * @return the current SmesMessageSpecification + */ + public SmesMessageSpecification setValidityPeriod(String v) { + if (!nullHeaderWillOverwriteDefault(v)) + this.validityPeriod = v; + return this; + } + + public SmesMessageSpecification setScheduleDeliveryTime(Date d) { + if (!nullHeaderWillOverwriteDefault(d)) + this.scheduleDeliveryTime = timeFormatter.format(d); + return this; + } + + public SmesMessageSpecification setRegisteredDelivery(RegisteredDelivery rd) { + if (!nullHeaderWillOverwriteDefault(rd)) + this.registeredDelivery = rd; + return this; + } + + public SmesMessageSpecification setReplaceIfPresentFlag(byte replaceIfPresentFlag) { + if (!nullHeaderWillOverwriteDefault(replaceIfPresentFlag)) + this.replaceIfPresentFlag = replaceIfPresentFlag; + return this; + } + + public SmesMessageSpecification setDataCoding(DataCoding dataCoding) { + if (!nullHeaderWillOverwriteDefault(dataCoding)) + this.dataCoding = dataCoding; + return this; + } + + public SmesMessageSpecification setSmDefaultMsgId(byte smDefaultMsgId) { + this.smDefaultMsgId = smDefaultMsgId; + return this; + } + + public SmesMessageSpecification setTimeFormatter(TimeFormatter timeFormatter) { + if (!nullHeaderWillOverwriteDefault(timeFormatter)) + this.timeFormatter = timeFormatter; + return this; + } + + /** + * todo it'running not quite true that the payload needs to be 140c. A large message can be split up into smaller messages, + * but for now it'running more useful to have this validation in place than not. + * + * @param s the text message body + * @return the SmesMessageSpecification + */ + public SmesMessageSpecification setShortTextMessage(String s) { + Assert.notNull(s, "the SMS message payload must not be null"); + Assert.isTrue(s.length() <= this.maxLengthSmsMessages, "the SMS message payload must be 140 characters or less."); + this.shortMessage = s.getBytes(); + return this; + } + + /** + * this is a good value, but not strictly speaking universal. This is intended only for exceptional configuration cases + *

+ * See: http://www.nowsms.com/long-sms-text-messages-and-the-160-character-limit + * + * @param maxLengthSmsMessages the length of sms messages + * @see #setShortTextMessage(String) + */ + public void setMaxLengthSmsMessages(int maxLengthSmsMessages) { + this.maxLengthSmsMessages = maxLengthSmsMessages; + } + + /** + * Resets the thread local, pooled objects to a known state before reuse. + *

+ * Resetting the variables is trivially cheap compared to proxying a new one each time. + * + * @return the cleaned up {@link SmesMessageSpecification} + */ + protected SmesMessageSpecification reset() { + + // configuration params - should they be reset? + maxLengthSmsMessages = 140; + timeFormatter = new AbsoluteTimeFormatter(); + + sourceAddress = null; + destinationAddress = null; + serviceType = "CMT"; + sourceAddressTypeOfNumber = TypeOfNumber.UNKNOWN; + sourceAddressNumberingPlanIndicator = NumberingPlanIndicator.UNKNOWN; + destinationAddressTypeOfNumber = TypeOfNumber.UNKNOWN; + destinationAddressNumberingPlanIndicator = NumberingPlanIndicator.UNKNOWN; + esmClass = new ESMClass(); + protocolId = 0; + priorityFlag = 1; + scheduleDeliveryTime = null; + validityPeriod = null; + registeredDelivery = new RegisteredDelivery(SMSCDeliveryReceipt.DEFAULT); + replaceIfPresentFlag = 0; + dataCoding = new GeneralDataCoding(Alphabet.ALPHA_DEFAULT, MessageClass.CLASS1, false); + smDefaultMsgId = 0; + shortMessage = null; // the bytes to the 140 character text message + smppSession = null; + return this; + } + + public SmesMessageSpecification setSourceAddressIfRequired(String defaultSourceAddress) { + if (!StringUtils.hasText(this.sourceAddress)) + this.sourceAddress = defaultSourceAddress; + return this; + } +} + +/* private static String fromPropertyToHeaderConstant(String n) { + + StringBuffer stringBuffer = new StringBuffer(); + + for (char c : n.toCharArray()) { + if (Character.isUpperCase(c)) { + stringBuffer.append("_"); + } + stringBuffer.append(c); + } + + String nn = stringBuffer.toString().toUpperCase(); + + String is = "IS_", + get = "GET_"; + if (nn.startsWith(is)) nn = nn.substring(is.length()); + if (nn.startsWith(get)) nn = nn.substring(get.length()); + + return nn; + } + + static public void main(String[] args) throws Throwable { + String m = "mb.setHeader( SmppConstants.%s, dsm.%s() );"; + String h = "public static final String %s = \"%s\";"; + Set marshalling = new HashSet(); + Set headers = new HashSet(); + PropertyDescriptor[] pds = PropertyUtils.getPropertyDescriptors(DeliverSm.class); + for (PropertyDescriptor propertyDescriptor : pds) { + Method reader = propertyDescriptor.getReadMethod(); + String readerName = reader.getName(); + + String header = fromPropertyToHeaderConstant(readerName); + headers.add(header); + marshalling.add(readerName + ":" + header); + } + + for (String s : headers) System.outSession.println(String.format(h, s, s)); + + for (String s : marshalling) { + + String[] tuple = s.split(":"); + + System.outSession.println(String.format(m, tuple[1], tuple[0])); + } + } +*/ diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppConstants.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppConstants.java new file mode 100644 index 0000000..c5bed67 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppConstants.java @@ -0,0 +1,52 @@ +package org.springframework.integration.smpp.core; + +/** + * + * + * @author Josh Long + */ +public abstract class SmppConstants { + + public static final String SMS="SMS", SRC_ADDR = "SRC_ADDRESS", DST_ADDR = "DEST_ADDRESS", SMS_MSG = "SMS_SHORT_MESSAGE"; + + public static final String REGISTERED_DELIVERY_MODE = "REGISTERED_DELIVERY_MODE", REPLACE_IF_PRESENT_FLAG = "REPLACE_IF_PRESENT_FLAG"; + public static final String SRC_TON = "SRC_ADDR_TON", DST_TON = "DST_ADDR_TON", DST_NPI = "DST_ADDR_NPI", SRC_NPI = "SRC_ADDR_NPI"; + public static final String SCHEDULED_DELIVERY_TIME = "SCHEDULED_DELIVERY_TIME"; + public static final String SOURCE_ADDR_NPI = "SOURCE_ADDR_NPI"; + public static final String PRIORITY_FLAG = "PRIORITY_FLAG"; + public static final String COMMAND_LENGTH = "COMMAND_LENGTH"; + public static final String UDHI_AND_REPLY_PATH = "UDHI_AND_REPLY_PATH"; + public static final String SEQUENCE_NUMBER = "SEQUENCE_NUMBER"; + public static final String SME_MANUAL_ACK_REQUESTED = "SME_MANUAL_ACK_REQUESTED"; + public static final String DEST_ADDR_TON = "DEST_ADDR_TON"; + public static final String COMMAND_ID = "COMMAND_ID"; + public static final String SME_DELIVERY_AND_MANUAL_ACK_REQUESTED = "SME_DELIVERY_AND_MANUAL_ACK_REQUESTED"; + public static final String VALIDITY_PERIOD = "VALIDITY_PERIOD"; + public static final String SOURCE_ADDR = SRC_ADDR; + public static final String ESM_CLASS = "ESM_CLASS"; + public static final String PROTOCOL_ID = "PROTOCOL_ID"; + public static final String SERVICE_TYPE = "SERVICE_TYPE"; + public static final String COMMAND_ID_AS_HEX = "COMMAND_ID_AS_HEX"; + public static final String CONVERSATION_ABORT = "CONVERSATION_ABORT"; + public static final String SME_ACK_NOT_REQUESTED = "SME_ACK_NOT_REQUESTED"; + public static final String DEST_ADDR_NPI = "DEST_ADDR_NPI"; + public static final String REPLACE_IF_PRESENT = "REPLACE_IF_PRESENT"; + public static final String SMSC_DELIVERY_RECEIPT = "SMSC_DELIVERY_RECEIPT"; + public static final String INTERMEDIATE_DELIVERY_NOTIFICATION = "INTERMEDIATE_DELIVERY_NOTIFICATION"; + public static final String REGISTERED_DELIVERY = "REGISTERED_DELIVERY"; +// public static final String SHORT_MESSAGE_AS_DELIVERY_RECEIPT = "SHORT_MESSAGE_AS_DELIVERY_RECEIPT"; + public static final String SCHEDULE_DELIVERY_TIME = "SCHEDULE_DELIVERY_TIME"; + public static final String COMMAND_STATUS = "COMMAND_STATUS"; + public static final String SHORT_MESSAGE = "SHORT_MESSAGE"; + public static final String SME_MANUAL_ACKNOWLEDGMENT = "SME_MANUAL_ACKNOWLEDGMENT"; + public static final String COMMAND_STATUS_AS_HEX = "COMMAND_STATUS_AS_HEX"; + public static final String UDHI = "UDHI"; + public static final String SME_DELIVERY_ACK_REQUESTED = "SME_DELIVERY_ACK_REQUESTED"; + public static final String DATA_CODING = "DATA_CODING"; + public static final String SOURCE_ADDR_TON = "SOURCE_ADDR_TON"; + public static final String DEFAULT_MESSAGE_TYPE = "DEFAULT_MESSAGE_TYPE"; + public static final String SM_DEFAULT_MSG_ID = "SM_DEFAULT_MSG_ID"; + public static final String REPLY_PATH = "REPLY_PATH"; + public static final String DEST_ADDRESS = DST_ADDR; + public static final String OPTIONAL_PARAMETERS = "OPTIONAL_PARAMETERS"; +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppHeaders.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppHeaders.java new file mode 100644 index 0000000..6d3267e --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppHeaders.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.smpp.core; + +/** + * Smpp adapter specific message headers. + * + * @author Johanes Soetanto + * @since 2.2 + */ +public class SmppHeaders { + + private static final String PREFIX = "smpp_"; + + /** Non instantiable utility class */ + private SmppHeaders() { + throw new AssertionError(); + } + +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/package-info.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/package-info.java new file mode 100644 index 0000000..f5f05f9 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides core classes of the Smpp module. + */ +package org.springframework.integration.smpp.core; diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/SmppInboundChannelAdapter.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/SmppInboundChannelAdapter.java new file mode 100644 index 0000000..e9bf475 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/SmppInboundChannelAdapter.java @@ -0,0 +1,87 @@ +package org.springframework.integration.smpp.inbound; + +import org.jsmpp.bean.BindType; +import org.jsmpp.bean.DeliverSm; +import org.jsmpp.bean.DeliveryReceipt; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.endpoint.AbstractEndpoint; +import org.springframework.integration.smpp.core.AbstractReceivingMessageListener; +import org.springframework.integration.smpp.core.SmesMessageSpecification; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.util.Assert; + +/** + * Supports receiving messages of a payload specified by the SMPP protocol from a short message service center (SMSC). + * + * @author Josh Long + * @since 2.1 + *

+ * todo find some way to configure the {@link java.util.concurrent.Executor}running for the JSMPP library + */ +public class SmppInboundChannelAdapter extends AbstractEndpoint { + + private MessagingTemplate messagingTemplate; + private MessageChannel channel; + private ExtendedSmppSession smppSession; + + /** + * the channel on which inbound SMS messages should be delivered to Spring Integration components. + * + * @param channel the channel + */ + public void setChannel(MessageChannel channel) { + this.channel = channel; + this.messagingTemplate = new MessagingTemplate(this.channel); + } + + @Override + protected void onInit() throws Exception { + Assert.notNull(this.channel, "the 'channel' property must be set"); + Assert.notNull(this.smppSession, "the 'smppSession' property must be set"); + Assert.isTrue(this.smppSession.getBindType().isReceiveable() || + this.smppSession.getBindType().equals(BindType.BIND_TRX), + "this session's bind type should support " + + "receiving messages or both sending *and* receiving messages!"); + } + + /** + * Set smpp session + * @param s smpp session + */ + public void setSmppSession(ExtendedSmppSession s) { + this.smppSession = s; + } + + private AbstractReceivingMessageListener abstractReceivingMessageListener = + new AbstractReceivingMessageListener() { + @Override + protected void onDeliveryReceipt(DeliverSm deliverSm, String ogMessageId, DeliveryReceipt deliveryReceipt) throws Exception { + // noop don't care + } + + @Override + protected void onTextMessage(DeliverSm deliverSm, String txtMessage) throws Exception { + Message msg = SmesMessageSpecification.toMessageFromSms(deliverSm, txtMessage); + messagingTemplate.send(msg); + } + }; + + @Override + protected void doStart() { + this.smppSession.addMessageReceiverListener(this.abstractReceivingMessageListener); + this.smppSession.start(); + } + + @Override + protected void doStop() { + this.smppSession.stop(); + } + + + @Override + public String getComponentType() { + return "smpp:inbound-channel-adapter"; + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/SmppInboundGateway.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/SmppInboundGateway.java new file mode 100644 index 0000000..7e991c5 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/SmppInboundGateway.java @@ -0,0 +1,134 @@ +package org.springframework.integration.smpp.inbound; + +import org.jsmpp.bean.BindType; +import org.jsmpp.bean.DeliverSm; +import org.jsmpp.bean.DeliveryReceipt; +import org.jsmpp.bean.TypeOfNumber; +import org.springframework.integration.Message; +import org.springframework.integration.gateway.MessagingGatewaySupport; +import org.springframework.integration.smpp.core.AbstractReceivingMessageListener; +import org.springframework.integration.smpp.core.SmesMessageSpecification; +import org.springframework.integration.smpp.core.SmppConstants; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * what'running an inbound gateway in this case? Receive a text message and then automatically send a response. + * + * @author Josh Long + * @since 2.1 + */ +public class SmppInboundGateway extends MessagingGatewaySupport { + + private ExtendedSmppSession smppSession; + private TypeOfNumber defaultSourceAddressTypeOfNumber; + private String defaultSourceAddress; + + /** + * Set default source address type of number. + * @param defaultSourceAddressTypeOfNumber default address type of number. + */ + public void setDefaultSourceAddressTypeOfNumber(TypeOfNumber defaultSourceAddressTypeOfNumber) { + this.defaultSourceAddressTypeOfNumber = defaultSourceAddressTypeOfNumber; + } + + /** + * Set default source address. + * @param defaultSourceAddress default source address + */ + public void setDefaultSourceAddress(String defaultSourceAddress) { + this.defaultSourceAddress = defaultSourceAddress; + } + + /** + * for configuration purposes. + * + * @param s the session to use + */ + public void setSmppSession(ExtendedSmppSession s) { + this.smppSession = s; + } + + @Override + protected void onInit() throws Exception { + Assert.notNull(this.smppSession, "the 'smppSession' property must be set"); + Assert.isTrue(this.smppSession.getBindType().isReceiveable() || + this.smppSession.getBindType().equals(BindType.BIND_TRX), + "this session's bind type should support " + + "receiving messages or both sending *and* receiving messages!"); + } + + private AbstractReceivingMessageListener abstractReceivingMessageListener = + new AbstractReceivingMessageListener() { + @Override + protected void onDeliveryReceipt(DeliverSm deliverSm, String ogMessageId, DeliveryReceipt deliveryReceipt) throws Exception { + // noop don't care + } + + @Override + protected void onTextMessage(DeliverSm deliverSm, String txtMessage) throws Exception { + // we receive sms + logger.debug("received an SMS in " + getClass() + ". Processing it."); + Message msg = SmesMessageSpecification.toMessageFromSms(deliverSm, txtMessage); + + // send it INTO SI, where it can be processed. The reply message is sent BACK to this, which we then send BACK outSession through SMS + logger.debug("sending the SMS inbound to be processed; awaiting a reply."); + + Message response = sendAndReceiveMessage(msg); + logger.debug("received a reply message; will handle as in outbound adapter"); + + // todo copy all the code from the outbound adapter related to defaults + /// todo also make sure that we simply flip the inbound to outbound + applyDefaults(msg, response, SmesMessageSpecification.fromMessage(smppSession, response)).send(); + logger.debug("the reply SMS message has been sent."); + } + }; + + /** + * among other things this method simply 'flips' the src/dst + * + * @param request req + * @param response res + * @param smesMessageSpecification spec + * @return same spec reflecting new switches + */ + SmesMessageSpecification applyDefaults(Message request, Message response, SmesMessageSpecification smesMessageSpecification) { + + String from = null, to = null; + if (request.getHeaders().containsKey(SmppConstants.SRC_ADDR)) { + to = (String) request.getHeaders().get(SmppConstants.SRC_ADDR); + if (StringUtils.hasText(to)) + smesMessageSpecification.setDestinationAddress(to); + } + if (request.getHeaders().containsKey(SmppConstants.DEST_ADDRESS)) { + from = (String) request.getHeaders().get(SmppConstants.DEST_ADDRESS); + if (StringUtils.hasText(from)) + smesMessageSpecification.setSourceAddressIfRequired(from); + } + if (defaultSourceAddressTypeOfNumber != null) + smesMessageSpecification.setSourceAddressTypeOfNumberIfRequired(this.defaultSourceAddressTypeOfNumber); + + if (StringUtils.hasText(this.defaultSourceAddress)) + smesMessageSpecification.setSourceAddressIfRequired(this.defaultSourceAddress); + return smesMessageSpecification; + } + + @Override + protected void doStart() { + super.doStart(); + this.smppSession.addMessageReceiverListener(this.abstractReceivingMessageListener); + this.smppSession.start(); + } + + @Override + protected void doStop() { + super.doStop(); + this.smppSession.stop(); + } + + @Override + public String getComponentType() { + return "smpp:inbound-gateway"; + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/package-info.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/package-info.java new file mode 100644 index 0000000..aed4690 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides inbound Spring Integration Smpp components. + */ +package org.springframework.integration.smpp.inbound; diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundChannelAdapter.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundChannelAdapter.java new file mode 100644 index 0000000..ebad814 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundChannelAdapter.java @@ -0,0 +1,106 @@ +package org.springframework.integration.smpp.outbound; + +import org.jsmpp.bean.BindType; +import org.jsmpp.bean.TypeOfNumber; +import org.jsmpp.util.AbsoluteTimeFormatter; +import org.jsmpp.util.TimeFormatter; +import org.springframework.integration.Message; +import org.springframework.integration.MessagingException; +import org.springframework.integration.context.IntegrationObjectSupport; +import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.smpp.core.SmesMessageSpecification; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Sends messages to an SMS gateway using SMPP. Most of the work in terms of converting inbound message headers + * (whose keys, by the way, live in {@link org.springframework.integration.smpp.core.SmppConstants}) is done by {@link org.springframework.integration.smpp.core.SmesMessageSpecification}, which + * handles all the tedium of converting and validating the configuration. + *

+ * This adapter supports mobile terminated (MT) messaging, where the recipient is a directory phone number. + * + * @author Josh Long + * @since 2.1 + */ +public class SmppOutboundChannelAdapter extends IntegrationObjectSupport implements MessageHandler { + + private String defaultSourceAddress; + + private TypeOfNumber defaultSourceAddressTypeOfNumber = TypeOfNumber.UNKNOWN; + + private TimeFormatter timeFormatter = new AbsoluteTimeFormatter(); + + private ExtendedSmppSession smppSession; + + @SuppressWarnings("unused") + public void setDefaultSourceAddress(String defaultSourceAddress) { + this.defaultSourceAddress = defaultSourceAddress; + } + + @SuppressWarnings("unused") + public void setDefaultSourceAddressTypeOfNumber(TypeOfNumber defaultSourceAddressTypeOfNumber) { + this.defaultSourceAddressTypeOfNumber = defaultSourceAddressTypeOfNumber; + } + + @SuppressWarnings("unused") + public void setTimeFormatter(TimeFormatter timeFormatter) { + this.timeFormatter = timeFormatter; + } + + @Override + protected void onInit() throws Exception { + if (this.timeFormatter == null) { + this.timeFormatter = new AbsoluteTimeFormatter(); + } + + Assert.notNull(this.smppSession, "the smppSession must not be null"); + Assert.isTrue(!this.smppSession.getBindType().equals(BindType.BIND_RX), + "the BindType must support message production: BindType.TX or BindType.TRX only supported"); + + this.smppSession.start(); + + } + + private SmesMessageSpecification applyDefaultsIfNecessary(SmesMessageSpecification smsSpec) { + + if (defaultSourceAddressTypeOfNumber != null) + smsSpec.setSourceAddressTypeOfNumberIfRequired(this.defaultSourceAddressTypeOfNumber); + + if (StringUtils.hasText(this.defaultSourceAddress)) + smsSpec.setSourceAddressIfRequired(this.defaultSourceAddress); + + return smsSpec; + } + + public void setSmppSession(ExtendedSmppSession s) { + this.smppSession = s; + } + + @Override + public void handleMessage(Message message) throws MessagingException { + + try { + // todo support a gateway and have that gateway also handle message delivery receipt notifications + // that will correlate this smsMessageId with the ID that comes back asynchronously from the SMSC indicating that + // the message has been delivered. + // this could require that we keep a correlation map since its possible upstream SMSC + // unused return value -- see gateway + + SmesMessageSpecification specification = applyDefaultsIfNecessary( + SmesMessageSpecification.fromMessage(this.smppSession, message) + .setTimeFormatter(this.timeFormatter)); + + String smsMessageId = specification.send(); + logger.debug( "sent message : "+message.getPayload()); + logger.debug("message ID for the sent message is: " + smsMessageId); + } catch (Exception e) { + throw new RuntimeException("Exception in trying to process the inbound SMPP message", e); + } + } + + @Override + public String getComponentType() { + return "smpp:outbound-channel-adapter"; + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundGateway.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundGateway.java new file mode 100644 index 0000000..1f4a3d1 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundGateway.java @@ -0,0 +1,102 @@ +package org.springframework.integration.smpp.outbound; + +import org.jsmpp.bean.BindType; +import org.jsmpp.bean.TypeOfNumber; +import org.jsmpp.util.AbsoluteTimeFormatter; +import org.jsmpp.util.TimeFormatter; +import org.springframework.integration.Message; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.smpp.core.SmesMessageSpecification; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Support for request/reply exchanges over SMPP to a SMSC. + *

+ * The request is an outbound SMS message, as in the {@link org.springframework.integration.smpp.outbound.SmppOutboundChannelAdapter}, + * and the reply can either be the messageId of the outbound message which can ultimately be used to track the confirmation, + * or the confirmation of the receipt of the outbound message itself. In the latter case, this class simply does the work + * of waiting for the reply and correlating it to the outbound request. + *

+ * By default this component assumes one {@link org.jsmpp.session.SMPPSession} in "transceiver" mode - it can both request and reply. + * Conceptually it should be possible to support two {@link org.jsmpp.session.SMPPSession}running, one in "sender" mode, and another in + * "receiver" mode and handle the duplexing manually. The correlation logic is the same, in any event. + *

+ * + * @author Josh Long + * @since 2.1 + */ +public class SmppOutboundGateway extends AbstractReplyProducingMessageHandler { + @Override + protected void onInit() { + Assert.isTrue( + this.smppSession.getBindType().equals(BindType.BIND_TX) || + this.smppSession.getBindType().equals(BindType.BIND_TRX), + "the smppSession's bindType must be BindType.BIND_TX or BindType.BIND_TRX"); + + this.smppSession.start(); + } + + @Override + protected Object handleRequestMessage(Message requestMessage) { + try { + + SmesMessageSpecification specification = applyDefaultsIfNecessary( + SmesMessageSpecification.fromMessage(this.smppSession, requestMessage) + .setTimeFormatter(this.timeFormatter)); + + String smsMessageId = specification.send(); + + logger.debug("message ID for the sent message is: " + smsMessageId); + + return MessageBuilder.withPayload(smsMessageId).build(); + } catch (Exception e) { + throw new RuntimeException("Exception in trying to process the inbound SMPP message", e); + } + } + + private String defaultSourceAddress; + + private TypeOfNumber defaultSourceAddressTypeOfNumber = TypeOfNumber.UNKNOWN; + + private TimeFormatter timeFormatter = new AbsoluteTimeFormatter(); + + private ExtendedSmppSession smppSession; + + @SuppressWarnings("unused") + public void setDefaultSourceAddress(String defaultSourceAddress) { + this.defaultSourceAddress = defaultSourceAddress; + } + + @SuppressWarnings("unused") + public void setDefaultSourceAddressTypeOfNumber(TypeOfNumber defaultSourceAddressTypeOfNumber) { + this.defaultSourceAddressTypeOfNumber = defaultSourceAddressTypeOfNumber; + } + + @SuppressWarnings("unused") + public void setTimeFormatter(TimeFormatter timeFormatter) { + this.timeFormatter = timeFormatter; + } + + private SmesMessageSpecification applyDefaultsIfNecessary(SmesMessageSpecification smsSpec) { + + if (defaultSourceAddressTypeOfNumber != null) + smsSpec.setSourceAddressTypeOfNumberIfRequired(this.defaultSourceAddressTypeOfNumber); + + if (StringUtils.hasText(this.defaultSourceAddress)) + smsSpec.setSourceAddressIfRequired(this.defaultSourceAddress); + + return smsSpec; + } + + public void setSmppSession(ExtendedSmppSession s) { + this.smppSession = s; + } + + @Override + public String getComponentType() { + return "smpp:outbound-gateway"; + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/package-info.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/package-info.java new file mode 100644 index 0000000..0388b21 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides Spring Integration components for doing outbound operations. + */ +package org.springframework.integration.smpp.outbound; diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/package-info.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/package-info.java new file mode 100644 index 0000000..78663da --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/package-info.java @@ -0,0 +1,4 @@ +/** + * Root package of the Smpp Module. + */ +package org.springframework.integration.smpp; diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/DelegatingMessageReceiverListener.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/DelegatingMessageReceiverListener.java new file mode 100644 index 0000000..6fdf8f4 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/DelegatingMessageReceiverListener.java @@ -0,0 +1,52 @@ +package org.springframework.integration.smpp.session; + +import org.jsmpp.bean.AlertNotification; +import org.jsmpp.bean.DataSm; +import org.jsmpp.bean.DeliverSm; +import org.jsmpp.extra.ProcessRequestException; +import org.jsmpp.session.DataSmResult; +import org.jsmpp.session.MessageReceiverListener; +import org.jsmpp.session.Session; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * We're normally allowed to register only one {@link MessageReceiverListener} instance. + * Additionally, that instance must be registered before connection. + *

+ * This class delegates all calls to as many {@link MessageReceiverListener}s as you'd like, regardless of when the registered listener was added. + * + * @author Josh Long + * @since 2.1 + */ +public class DelegatingMessageReceiverListener implements MessageReceiverListener { + + private volatile Set messageReceiverListenerSet = + new CopyOnWriteArraySet(); + + public void onAcceptDeliverSm(DeliverSm deliverSm) throws ProcessRequestException { + for (MessageReceiverListener l : this.messageReceiverListenerSet) + l.onAcceptDeliverSm(deliverSm); + } + + public void onAcceptAlertNotification(AlertNotification alertNotification) { + for (MessageReceiverListener l : this.messageReceiverListenerSet) + l.onAcceptAlertNotification(alertNotification); + } + + public DataSmResult onAcceptDataSm(DataSm dataSm, Session source) throws ProcessRequestException { + DataSmResult dataSmResult = null; + for (MessageReceiverListener l : this.messageReceiverListenerSet) { + DataSmResult tmpV = l.onAcceptDataSm(dataSm, source); + if (tmpV != null) { + dataSmResult = tmpV; + } + } + return dataSmResult; // could still be null + } + + public void addMessageReceiverListener(MessageReceiverListener messageReceiverListener) { + this.messageReceiverListenerSet.add(messageReceiverListener); + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/ExtendedSmppSession.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/ExtendedSmppSession.java new file mode 100644 index 0000000..f3c38da --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/ExtendedSmppSession.java @@ -0,0 +1,37 @@ +package org.springframework.integration.smpp.session; + +import org.jsmpp.bean.BindType; +import org.jsmpp.session.ClientSession; +import org.jsmpp.session.MessageReceiverListener; + +/** + * Represents an {@link org.jsmpp.session.SMPPSession} that has a few extra capabilities: + *

+ *

  1. supports registration of multiple {@link org.jsmpp.session.MessageReceiverListener}s
+ * + * @author Josh Long + * @since 2.1 + */ +public interface ExtendedSmppSession extends ClientSession { + /** + * a {@link MessageReceiverListener} implementation to be added to the set of existing listeners. + *

+ * NB: the contract for each of these is the same as for a single instance: don't take too long when doing your processing. This is even more + * important now that multiple implementations need to share the same callback slice time. + * + * @param messageReceiverListener the message receiver listener + */ + void addMessageReceiverListener(MessageReceiverListener messageReceiverListener); + + /** + * We need to know this to determine whether or not this session can handle the requirements we need. + * + * @return the {@link BindType} + */ + BindType getBindType(); + + void start() ; + + void stop() ; + +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/ExtendedSmppSessionAdaptingDelegate.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/ExtendedSmppSessionAdaptingDelegate.java new file mode 100644 index 0000000..131d491 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/ExtendedSmppSessionAdaptingDelegate.java @@ -0,0 +1,170 @@ +package org.springframework.integration.smpp.session; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.InvalidResponseException; +import org.jsmpp.PDUException; +import org.jsmpp.bean.*; +import org.jsmpp.extra.NegativeResponseException; +import org.jsmpp.extra.ResponseTimeoutException; +import org.jsmpp.extra.SessionState; +import org.jsmpp.session.*; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.Lifecycle; + +import java.io.IOException; + +/** + * Adapts to the {@link ClientSession} API, while also providing the callbacks for the Spring container + * + * @author Josh Long + * @since 2.1 + */ +public class ExtendedSmppSessionAdaptingDelegate implements /*Lifecycle,*/ ExtendedSmppSession, InitializingBean { + + /** + * callback for custom lifecycle events + */ + private Lifecycle lifecycle; + private Log log = LogFactory.getLog(getClass()); + private final DelegatingMessageReceiverListener delegatingMessageReceiverListener = new DelegatingMessageReceiverListener(); + private volatile boolean running; + private BindType bindType; + private SMPPSession session; + + public void setBindType(BindType bindType) { + this.bindType = bindType; + } + + public SMPPSession getTargetClientSession() { + return this.session; + } + + public void start() { + + if( this.running) + return; + + lifecycle.start(); + this.running = true; + } + + public void stop() { + lifecycle.stop(); + this.running = false; + } + + public boolean isRunning() { + return this.running; + } + + public BindType getBindType() { + return this.bindType; + } + + /** + * noops for the {@link Lifecycle} arg in {@link ExtendedSmppSessionAdaptingDelegate#ExtendedSmppSessionAdaptingDelegate(org.jsmpp.session.SMPPSession, org.springframework.context.Lifecycle)} + * + * @param session the session + */ + public ExtendedSmppSessionAdaptingDelegate(SMPPSession session) { + this(session, new Lifecycle() { + public void start() { + } + + public void stop() { + } + + public boolean isRunning() { + return true; + } + }); + } + + public ExtendedSmppSessionAdaptingDelegate(SMPPSession session, Lifecycle lifecycle) { + this.lifecycle = lifecycle; + this.session = session; + this.session.setMessageReceiverListener(this.delegatingMessageReceiverListener); + } + + public void addMessageReceiverListener(MessageReceiverListener messageReceiverListener) { + this.delegatingMessageReceiverListener.addMessageReceiverListener(messageReceiverListener); + } + + public String submitShortMessage(String serviceType, TypeOfNumber sourceAddrTon, NumberingPlanIndicator sourceAddrNpi, + String sourceAddr, TypeOfNumber destAddrTon, NumberingPlanIndicator destAddrNpi, + String destinationAddr, ESMClass esmClass, byte protocolId, byte priorityFlag, String scheduleDeliveryTime, String validityPeriod, RegisteredDelivery registeredDelivery, byte replaceIfPresentFlag, DataCoding dataCoding, byte smDefaultMsgId, byte[] shortMessage, OptionalParameter... optionalParameters) throws PDUException, ResponseTimeoutException, InvalidResponseException, NegativeResponseException, IOException { + return session.submitShortMessage(serviceType, sourceAddrTon, sourceAddrNpi, sourceAddr, destAddrTon, destAddrNpi, destinationAddr, esmClass, protocolId, priorityFlag, scheduleDeliveryTime, validityPeriod, registeredDelivery, replaceIfPresentFlag, dataCoding, smDefaultMsgId, shortMessage, optionalParameters); + } + + public SubmitMultiResult submitMultiple(String serviceType, TypeOfNumber sourceAddrTon, NumberingPlanIndicator sourceAddrNpi, String sourceAddr, Address[] destinationAddresses, ESMClass esmClass, byte protocolId, byte priorityFlag, String scheduleDeliveryTime, String validityPeriod, RegisteredDelivery registeredDelivery, ReplaceIfPresentFlag replaceIfPresentFlag, DataCoding dataCoding, byte smDefaultMsgId, byte[] shortMessage, OptionalParameter[] optionalParameters) throws PDUException, ResponseTimeoutException, InvalidResponseException, NegativeResponseException, IOException { + return session.submitMultiple( + serviceType, sourceAddrTon, sourceAddrNpi, sourceAddr, destinationAddresses, esmClass, protocolId, priorityFlag, scheduleDeliveryTime, validityPeriod, registeredDelivery, replaceIfPresentFlag, dataCoding, smDefaultMsgId, shortMessage, optionalParameters + ); + } + + public QuerySmResult queryShortMessage(String messageId, TypeOfNumber sourceAddrTon, NumberingPlanIndicator sourceAddrNpi, String sourceAddr) throws PDUException, ResponseTimeoutException, InvalidResponseException, NegativeResponseException, IOException { + return session.queryShortMessage(messageId, sourceAddrTon, sourceAddrNpi, sourceAddr); + } + + public void cancelShortMessage(String serviceType, String messageId, TypeOfNumber sourceAddrTon, NumberingPlanIndicator sourceAddrNpi, String sourceAddr, + TypeOfNumber destAddrTon, NumberingPlanIndicator destAddrNpi, String destinationAddress) throws PDUException, ResponseTimeoutException, InvalidResponseException, NegativeResponseException, IOException { + session.cancelShortMessage(serviceType, messageId, sourceAddrTon, sourceAddrNpi, sourceAddr, destAddrTon, destAddrNpi, destinationAddress); + } + + public void replaceShortMessage(String messageId, TypeOfNumber sourceAddrTon, NumberingPlanIndicator sourceAddrNpi, String sourceAddr, String scheduleDeliveryTime, String validityPeriod, RegisteredDelivery registeredDelivery, byte smDefaultMsgId, byte[] shortMessage) throws PDUException, ResponseTimeoutException, InvalidResponseException, NegativeResponseException, IOException { + session.replaceShortMessage(messageId, sourceAddrTon, sourceAddrNpi, sourceAddr, scheduleDeliveryTime, validityPeriod, registeredDelivery, smDefaultMsgId, shortMessage); + } + + public DataSmResult dataShortMessage(String serviceType, TypeOfNumber sourceAddrTon, NumberingPlanIndicator sourceAddrNpi, String sourceAddr, TypeOfNumber destAddrTon, NumberingPlanIndicator destAddrNpi, String destinationAddr, ESMClass esmClass, RegisteredDelivery registeredDelivery, DataCoding dataCoding, OptionalParameter... optionalParameters) throws PDUException, ResponseTimeoutException, InvalidResponseException, NegativeResponseException, IOException { + return session.dataShortMessage(serviceType, sourceAddrTon, sourceAddrNpi, sourceAddr, destAddrTon, destAddrNpi, destinationAddr, esmClass, registeredDelivery, dataCoding, optionalParameters); + } + + public String getSessionId() { + return session.getSessionId(); + } + + public void setEnquireLinkTimer(int enquireLinkTimer) { + session.setEnquireLinkTimer(enquireLinkTimer); + } + + public int getEnquireLinkTimer() { + return session.getEnquireLinkTimer(); + } + + public void setTransactionTimer(long transactionTimer) { + session.setTransactionTimer(transactionTimer); + } + + public long getTransactionTimer() { + return session.getTransactionTimer(); + } + + public SessionState getSessionState() { + return session.getSessionState(); + } + + public void addSessionStateListener(SessionStateListener l) { + session.addSessionStateListener(l); + } + + public void removeSessionStateListener(SessionStateListener l) { + session.removeSessionStateListener(l); + } + + public long getLastActivityTimestamp() { + return session.getLastActivityTimestamp(); + } + + public void close() { + session.close(); + } + + public void unbindAndClose() { + session.unbindAndClose(); + } + + public void afterPropertiesSet() throws Exception { + log.debug( "afterPropertiesSet!"); + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/SmppSessionFactoryBean.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/SmppSessionFactoryBean.java new file mode 100644 index 0000000..080715b --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/SmppSessionFactoryBean.java @@ -0,0 +1,356 @@ +package org.springframework.integration.smpp.session; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.DefaultPDUReader; +import org.jsmpp.DefaultPDUSender; +import org.jsmpp.SynchronizedPDUSender; +import org.jsmpp.bean.BindType; +import org.jsmpp.bean.NumberingPlanIndicator; +import org.jsmpp.bean.TypeOfNumber; +import org.jsmpp.session.MessageReceiverListener; +import org.jsmpp.session.SMPPSession; +import org.jsmpp.session.SessionStateListener; +import org.jsmpp.session.connection.Connection; +import org.jsmpp.session.connection.ConnectionFactory; +import org.jsmpp.session.connection.socket.SocketConnection; +import org.jsmpp.util.DefaultComposer; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.Lifecycle; +import org.springframework.context.SmartLifecycle; +import org.springframework.core.Ordered; +import org.springframework.util.Assert; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLSocketFactory; +import java.io.IOException; +import java.net.Socket; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Factory bean to create a {@link SMPPSession}. Usually, you need little more than the {@link #host}, + * the {@link #port}, perhaps a {@link #password}, and a {@link #systemId}. + *

+ * The {@link SMPPSession } represents a connection to a SMSC, through which SMS messages are sent and received. + *

+ * Here is a breakdown of the supported parameters on this factory bean: + *

+ * host the SMSC host to which the session is bound (think of this as the host of your email server) + * port the SMSC port to which the session is bound (think of this as a port on your email server) + * bindType values of type {@link org.jsmpp.bean.BindType}. the bind type specifies whether this {@link SMPPSession} can send ({@link org.jsmpp.bean.BindType#BIND_TX}), receive ({@link org.jsmpp.bean.BindType#BIND_RX}), or both send and receive ({@link org.jsmpp.bean.BindType#BIND_TRX}). + * systemId the system ID for the server being bound to + * password the password for the server being bound to + * systemType the SMSC system type + * addrTon a value from the {@link org.jsmpp.bean.TypeOfNumber} enumeration. default is {@link org.jsmpp.bean.TypeOfNumber#UNKNOWN} + * addrNpi a value from the {@link org.jsmpp.bean.NumberingPlanIndicator} enumeration. Default is {@link org.jsmpp.bean.NumberingPlanIndicator#UNKNOWN} + * addressRange can be null. Specifies the address range. + * timeout a good default value is 60000 (1 minute) + * + * @author Josh Long + *

+ * todo support a proxied SMPPSession that automatically recovers from disconnects a la the examples {@link org.jsmpp.examples.gateway.AutoReconnectGateway} + * @see org.jsmpp.session.SMPPSession#SMPPSession() + * @see org.jsmpp.session.SMPPSession#connectAndBind(String, int, org.jsmpp.session.BindParameter) + * @see org.jsmpp.session.SMPPSession#connectAndBind(String, int, org.jsmpp.bean.BindType, String, String, String, org.jsmpp.bean.TypeOfNumber, org.jsmpp.bean.NumberingPlanIndicator, String, long) + * @since 2.1 + */ +public class SmppSessionFactoryBean implements FactoryBean, SmartLifecycle, InitializingBean { + + /** + * impl of {@link Lifecycle} that connects and disconnects respectively in + * {@link org.springframework.context.Lifecycle#start()} and {@link org.springframework.context.Lifecycle#stop()} + * + * @author Josh Long + */ + private Set messageReceiverListeners = new HashSet(); + private boolean autoStartup; + private volatile boolean running; + private Log log = LogFactory.getLog(getClass()); + private SessionStateListener sessionStateListener; + private boolean ssl = false; + private String host = "127.0.0.1"; + private String addressRange; + private long timeout = 60 * 1000;// 1 minute + private int port = 2775; // good default though this has been known to change + private BindType bindType = BindType.BIND_TRX; // bind as a 'transceiver' - only 3.4 of the spec requires support for this + private String systemId = getClass().getSimpleName().toLowerCase(); // what would typically be called 'user' in a user/pw scheme + private String password; + private String systemType = "cp"; + private TypeOfNumber addrTon = TypeOfNumber.UNKNOWN; + private NumberingPlanIndicator addrNpi = NumberingPlanIndicator.UNKNOWN; + + private ExtendedSmppSessionAdaptingDelegate product; + + public void setSsl(boolean ssl) { + this.ssl = ssl; + } + + public void setHost(String host) { + this.host = host; + } + + public void setPort(int port) { + this.port = port; + } + + public void setBindType(BindType bindType) { + this.bindType = bindType; + } + + public void setSystemId(String systemId) { + this.systemId = systemId; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setSystemType(String systemType) { + this.systemType = systemType; + } + + public void setAddrTon(TypeOfNumber addrTon) { + this.addrTon = addrTon; + } + + public void setAddrNpi(NumberingPlanIndicator addrNpi) { + this.addrNpi = addrNpi; + } + + /** + * this specifies the range of numbers we want to listen to - as a consumer. If you + * specify '1234' as a destination address, and want to listen / receive all messages sent + * to that number, then specify '1234' as the {@link #addressRange}. + * + * @param addressRange the range of phone numbers to receive from. + */ + public void setAddressRange(String addressRange) { + this.addressRange = addressRange; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public void setSessionStateListener(SessionStateListener sessionStateListener) { + this.sessionStateListener = sessionStateListener; + } + + public void setMessageReceiverListeners(MessageReceiverListener... listeners) { + setMessageReceiverListeners(new HashSet(Arrays.asList(listeners))); + } + + public void setMessageReceiverListeners(Set messageReceiverListeners) { + this.messageReceiverListeners = messageReceiverListeners; + } + + /** + * @return the configured SMPPSession + * @throws Exception should anything go wrong + */ + private ExtendedSmppSessionAdaptingDelegate buildSmppSession() throws Exception { + SMPPSession smppSession = null; + if (!ssl) { + smppSession = new SMPPSession(); + } else { + smppSession = new SMPPSession(new SynchronizedPDUSender(new DefaultPDUSender(new DefaultComposer())), new DefaultPDUReader(), sslConnectionFactory); + } + + ExtendedSmppSessionAdaptingDelegate extendedSmppSessionAdaptingDelegate = new ExtendedSmppSessionAdaptingDelegate(smppSession, new ConnectingLifecycle(smppSession)); + + for (MessageReceiverListener mrl : this.messageReceiverListeners) + extendedSmppSessionAdaptingDelegate.addMessageReceiverListener(mrl); + + extendedSmppSessionAdaptingDelegate.setBindType(this.bindType); + return extendedSmppSessionAdaptingDelegate; + } + + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + /** + * {@inheritDoc} + */ + public boolean isAutoStartup() { + return this.autoStartup; + } + + /** + * {@inheritDoc} + */ + public void stop(Runnable callback) { + try { + log.debug("shutting down in " + getClass().getName() + "#stop(Runnable)."); + callback.run(); + } catch (Throwable throwable) { + log.warn("error when trying to shutdown " + getClass().getName() + ", could not invoke the callback's Runnable#run method"); + } + this.stop(); + } + + /** + * {@inheritDoc} + */ + public void start() { + log.debug("starting up in " + getClass().getName() + "#start()."); + ( product).start(); + this.running = true; + } + + /** + * {@inheritDoc} + */ + public void stop() { + log.debug("shutting down in " + getClass().getName() + "#stop()."); + ( product).stop(); + this.running = false; + } + + /** + * {@inheritDoc} + */ + public boolean isRunning() { + return this.running; + } + + /** + * {@inheritDoc} + */ + public int getPhase() { + return Ordered.LOWEST_PRECEDENCE; + } + + /** + * {@inheritDoc} + *

+ * delegates to {@link #buildSmppSession()} + */ + public ExtendedSmppSession getObject() throws Exception { + return product; + } + + /** + * {@inheritDoc} + */ + public Class getObjectType() { + return ExtendedSmppSessionAdaptingDelegate.class; + } + + /** + * {@inheritDoc} + */ + public boolean isSingleton() { + return true; + } + + /** + * {@inheritDoc} + */ + public void afterPropertiesSet() throws Exception { + + // NB, the reference handed back by {@link org.springframework.beans.factory.FactoryBean#getObject()} isn't itself + // managed, only the factory, so we cache it and then delegate through the factory's lifecycle methods. + + Assert.notNull(this.systemId, "the systemId can't be null"); + Assert.notNull(this.host, "the host can't be null"); + Assert.notNull(this.port, "the port can't be null"); + + this.product = buildSmppSession(); + } + + /** + * singleton {@link ConnectionFactory} that handles SSL + */ + final private static ConnectionFactory sslConnectionFactory = new ConnectionFactory() { + + public Connection createConnection(String host, int port) throws IOException { + SocketFactory socketFactory = SSLSocketFactory.getDefault(); + Socket socket = socketFactory.createSocket(host, port); + return new SocketConnection(socket); + } + }; + + /** + * lifecycle implementation that simply {@link SMPPSession#connectAndBind(String, int, org.jsmpp.session.BindParameter)} and + * {@link org.jsmpp.session.SMPPSession#unbindAndClose()}. + */ + private class ConnectingLifecycle implements Lifecycle { + + private volatile boolean running; + + private SMPPSession session; + + private ConnectingLifecycle(SMPPSession smppSession) { + this.session = smppSession; + } + + public boolean isRunning() { + return this.running; + } + + public void stop() { + if (session != null) { + if (session.getSessionState().isBound()) { + try { + session.unbindAndClose(); + } catch (Throwable t) { + log.warn("couldn't close and unbind the session", t); + } + } + } else { + log.warn("the smppSession given to close is null"); + } + } + + public void start() { + try { + session.connectAndBind(host, port, bindType, systemId, password, systemType, addrTon, addrNpi, addressRange, timeout); + this.running = true; + } catch (IOException e) { + log.error("something happened when trying to connect", e); + } + } + } +} + +/* private void reconnectAfter(final long timeInMillis) { + new Thread() { + @Override + public void run() { + logger.info("Schedule reconnect after " + timeInMillis + " millis"); + try { + Thread.sleep(timeInMillis); + } catch (InterruptedException e) { + } + + int attempt = 0; + while (session == null || session.getSessionState().equals(SessionState.CLOSED)) { + try { + logger.info("Reconnecting attempt #" + (++attempt) + "..."); + session = newSession(); + } catch (IOException e) { + logger.error("Failed opening connection and bind to " + remoteIpAddress + ":" + remotePort, e); + // wait for a second + try { Thread.sleep(1000); } catch (InterruptedException ee) {} + } + } + } + }.start(); + } + + + private class SessionStateListenerImpl implements SessionStateListener { + public void onStateChange(SessionState newState, SessionState oldState, + Object source) { + if (newState.equals(SessionState.CLOSED)) { + logger.info("Session closed"); + reconnectAfter(reconnectInterval); + } + } + } + + */ diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/package-info.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/package-info.java new file mode 100644 index 0000000..5968eac --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides various classes used for Spring Integration Smpp session. + */ +package org.springframework.integration.smpp.session; \ No newline at end of file diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/CurrentExecutingMethodHolder.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/CurrentExecutingMethodHolder.java new file mode 100644 index 0000000..c66f633 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/CurrentExecutingMethodHolder.java @@ -0,0 +1,44 @@ +package org.springframework.integration.smpp.util; + +import org.springframework.core.NamedThreadLocal; + +import java.lang.reflect.Method; + +/** + * A place to stash the currently executing method for advised classes. This provides the equivalent of JavaScript's arity property, + * and it's probably cheaper to implement than throwing an {@link Exception} and parsing its stack trace for the method whence the + * exception was thrown. + * + * @author Josh Long + * @since 2.1 + */ +abstract public class CurrentExecutingMethodHolder { + + public static ThreadLocal methodThreadLocal = new NamedThreadLocal("methodThreadLocal"); + + /** + * returns the currently executing thread local-bound method + * + * @return the currently executing method + */ + public static Method getCurrentlyExecutingMethod() { + return methodThreadLocal.get(); + } + + /** + * stash the currently execution method + * + * @param m the method in the throes of execution + */ + public static void setCurrentlyExecutingMethod(Method m) { + removeMethod(); + methodThreadLocal.set(m); + } + + /** + * the thread local method. + */ + public static void removeMethod() { + methodThreadLocal.remove(); + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/CurrentMethodExposingMethodInterceptor.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/CurrentMethodExposingMethodInterceptor.java new file mode 100644 index 0000000..063e9f8 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/CurrentMethodExposingMethodInterceptor.java @@ -0,0 +1,27 @@ +package org.springframework.integration.smpp.util; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +/** + * Simple method interceptor that does nothing but store the currently executing method and make it available for the duration + * of the invoked method, so that any class may introspect the currently running method + * without setting up a custom {@link MethodInterceptor} like this one. + *

+ * In a sense, this is like JavaScript's method arity feature. + * + * @author Josh Long + * @since 2.1 + */ +public class CurrentMethodExposingMethodInterceptor implements MethodInterceptor { + + @Override + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + try { + CurrentExecutingMethodHolder.setCurrentlyExecutingMethod(methodInvocation.getMethod()); + return methodInvocation.proceed(); + } finally { + CurrentExecutingMethodHolder.removeMethod(); + } + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/package-info.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/package-info.java new file mode 100644 index 0000000..2bb079f --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides various util classes used across Spring Integration Smpp Components. + */ +package org.springframework.integration.smpp.util; \ No newline at end of file diff --git a/spring-integration-smpp/src/main/resources/META-INF/spring.handlers b/spring-integration-smpp/src/main/resources/META-INF/spring.handlers new file mode 100644 index 0000000..456793c --- /dev/null +++ b/spring-integration-smpp/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/integration/smpp=org.springframework.integration.smpp.config.xml.SmppNamespaceHandler diff --git a/spring-integration-smpp/src/main/resources/META-INF/spring.schemas b/spring-integration-smpp/src/main/resources/META-INF/spring.schemas new file mode 100644 index 0000000..160c4f2 --- /dev/null +++ b/spring-integration-smpp/src/main/resources/META-INF/spring.schemas @@ -0,0 +1,2 @@ +http\://www.springframework.org/schema/integration/smpp/spring-integration-smpp-2.2.xsd=org/springframework/integration/smpp/config/xml/spring-integration-smpp-2.2.xsd +http\://www.springframework.org/schema/integration/smpp/spring-integration-smpp.xsd=org/springframework/integration/smpp/config/xml/spring-integration-smpp-2.2.xsd diff --git a/spring-integration-smpp/src/main/resources/META-INF/spring.tooling b/spring-integration-smpp/src/main/resources/META-INF/spring.tooling new file mode 100644 index 0000000..435cc7f --- /dev/null +++ b/spring-integration-smpp/src/main/resources/META-INF/spring.tooling @@ -0,0 +1,4 @@ +# Tooling related information for the integration Smpp namespace +http\://www.springframework.org/schema/integration/smpp@name=integration Smpp Namespace +http\://www.springframework.org/schema/integration/smpp@prefix=int-smpp +http\://www.springframework.org/schema/integration/smpp@icon=org/springframework/integration/smpp/config/xml/spring-integration-smpp.gif diff --git a/spring-integration-smpp/src/main/resources/org/springframework/integration/smpp/config/xml/spring-integration-smpp-2.2.xsd b/spring-integration-smpp/src/main/resources/org/springframework/integration/smpp/config/xml/spring-integration-smpp-2.2.xsd new file mode 100644 index 0000000..1b3766d --- /dev/null +++ b/spring-integration-smpp/src/main/resources/org/springframework/integration/smpp/config/xml/spring-integration-smpp-2.2.xsd @@ -0,0 +1,439 @@ + + + + + + + + + + + + + + + The definition for the Spring Integration Smpp + Inbound Channel Adapter. + + + + + + + + + + + Flag to indicate that the component should start automatically + on startup (default true). + + + + + + + + + + Channel which the sms will be put in, whey they come from the SMSC. + + + + + + + + + + + + + + + Defines the Spring Integration Smpp Inbound Gateway + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines an Outbound Channel Adapter. + + + + + + + + + + + + Channel from which messages will be output. + When a message is sent to this channel it will + cause the query + to be executed. + + + + + + + + + + + Source address will be used as sender for SMPP + + + + + + + The default source address Type of Number. Default is UNKNOWN. + + + + + + + + + + Reference to jsmpp time formatter + + + + + + + + + + + + + + + Defines the Spring Integration Smpp Outbound Gateway + + + + + + + + + Reference to jsmpp time formatter + + + + + + + + + + + + Specifies the order for invocation when this endpoint is connected as a + subscriber to a SubscribableChannel. + + + + + + + + + + + + Defines reference to smpp session + + + + + + + + + + + + + + + + Reference to extended smpp session + + + + + + + + + + + + + + + Defines the Spring Integration Smpp Session + + + + + + + + + + + Type of SMPP connection bind. + + + + + + + + Set enquire link timer (in milliseconds). + + + + + Set transaction timer (in milliseconds). + + + + + + Host to connect (default 127.0.0.1) + + + + + Address range we are listening to + + + + + Connection timeout (default 60000ms / 1 minute) + + + + + Port to connect (default 2775) + + + + + + + + The address Type of Number. Default is UNKNOWN. + + + + + + + + The address Numbering Plan Indicator. Default is UNKNOWN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bind as Transmitter (Sending Only) + + + + + + + Bind as Receiver (Receive Only) + + + + + + + Bind as Transceiver (Sending and Receive) + + + + + + + + + + + Identifies the underlying Spring bean definition, which is an + instance of either 'EventDrivenConsumer' or 'PollingConsumer', + depending on whether the component's input channel is a + 'SubscribableChannel' or 'PollableChannel'. + + + + + + + Reference to extended smpp session + + + + + + + + + + + + + + Defines common configuration for gateway adapters. + + + + + + + + + + + + + + + + + + + + + + + + + The receiving Message Channel of this endpoint. + + + + + + + + + + + + Source address will be used as sender for SMPP + + + + + + + The default source address Type of Number. Default is UNKNOWN. + + + + + + + + + diff --git a/spring-integration-smpp/src/main/resources/org/springframework/integration/smpp/config/xml/spring-integration-smpp.gif b/spring-integration-smpp/src/main/resources/org/springframework/integration/smpp/config/xml/spring-integration-smpp.gif new file mode 100644 index 0000000..41b369f Binary files /dev/null and b/spring-integration-smpp/src/main/resources/org/springframework/integration/smpp/config/xml/spring-integration-smpp.gif differ diff --git a/spring-integration-smpp/src/reference/docbook/history.xml b/spring-integration-smpp/src/reference/docbook/history.xml new file mode 100644 index 0000000..894f7f5 --- /dev/null +++ b/spring-integration-smpp/src/reference/docbook/history.xml @@ -0,0 +1,19 @@ + + + Change History + + + + + + + + + + + +
ReleaseDateChanges
2.2.02012.12.14Initial release migrated from Spring sandbox
+ +
diff --git a/spring-integration-smpp/src/reference/docbook/images/logo.png b/spring-integration-smpp/src/reference/docbook/images/logo.png new file mode 100644 index 0000000..8c71c13 Binary files /dev/null and b/spring-integration-smpp/src/reference/docbook/images/logo.png differ diff --git a/spring-integration-smpp/src/reference/docbook/index.xml b/spring-integration-smpp/src/reference/docbook/index.xml new file mode 100644 index 0000000..fb4c441 --- /dev/null +++ b/spring-integration-smpp/src/reference/docbook/index.xml @@ -0,0 +1,66 @@ + + + + Spring Integration Smpp Adapter + Smpp Adapter ${version} + Spring Integration + ${version} + + + + + + + + + + + + + + Josh Long + Johanes Soetanto + + + © SpringSource Inc., 2012 + + + + + + + What's new? + + + If you are interested in the changes and features, that were introduced in + earlier versions, please take a look at chapter: + + + + + + + + + + Integration Adapters + + + Spring Integration adapter for SMPP (Short Message Peer-to-Peer) includes + inbound channel adapter to receive sms from SMSC (Short Message Service Center), + outbound channel adapter to send sms through SMSC, and gateways to + send/receive from SMSC. + + + + + + Appendices + + Advanced Topics and Additional Resources + + + + diff --git a/spring-integration-smpp/src/reference/docbook/resources.xml b/spring-integration-smpp/src/reference/docbook/resources.xml new file mode 100644 index 0000000..678ca1b --- /dev/null +++ b/spring-integration-smpp/src/reference/docbook/resources.xml @@ -0,0 +1,32 @@ + + + Additional Resources + +

+ Spring Integration Home + + The definitive source of information about Spring Integration is the + Spring Integration Home at + http://www.springsource.org. That site serves as a hub of + information and is the best place to find up-to-date announcements about the project as well as links to + articles, blogs, and new sample applications. + +
+ +
+ SMPP Home + + You can get more information on SMPP from + + + This adapter uses jsmpp as the underlying library to communicate with SMPP. Jsmpp is Java implementation of + SMPP protocol v3.4 and can be retrieved from + + + You can get SMSC simulator from + + +
+ + diff --git a/spring-integration-smpp/src/reference/docbook/smpp.xml b/spring-integration-smpp/src/reference/docbook/smpp.xml new file mode 100644 index 0000000..dbecb94 --- /dev/null +++ b/spring-integration-smpp/src/reference/docbook/smpp.xml @@ -0,0 +1,85 @@ + + + Smpp Adapter + + The Spring Integration Smpp Adapter provides... + + + + Outbound Channel adapter + + + Outbound Gateway + + + Inbound Channel Adapter + + + Inbound Gateway + + + + + To use Spring Integration adapter for SMPP, you have to import the XML namespace. For example, you can + have following XML: + + + + + ]]> + + + + Meanwhile, you have to define your SMSC server information. For example you can define + server as following: + + + + + + //TODO: more documentation +
+ Outbound Channel Adapter + + Outbound channel adapter is to send sms into SMSC from channels in Spring Integration. + +
+ +
+ Inbound Channel Adapter + + Inbound channel adapter is used to receive sms from SMSC into Spring Integration channel. + +
+ +
+ Outbound Gateway + + Outbound gateway is similar to outbound channel adapter except that it can also be used to get + a result on the reply channel after sending. + +
+ +
+ Inbound Gateway + + Inbound gateway is similar to inbound channel adapter except that it can also be used to return + a result on the reply channel after receiving. + +
+ +
diff --git a/spring-integration-smpp/src/reference/docbook/whats-new.xml b/spring-integration-smpp/src/reference/docbook/whats-new.xml new file mode 100644 index 0000000..90a7765 --- /dev/null +++ b/spring-integration-smpp/src/reference/docbook/whats-new.xml @@ -0,0 +1,24 @@ + + + What's new? + + The Spring Integration adapter for SMPP includes two adapters and two gateways: + + + + Inbound Channel Adapter to receive sms from SMSC. + + + Outbound Channel Adapter to send sms through SMSC. + + + Inbound Gateway to receive sms from SMSC. + + + Outbound Gateway to send through SMSC. + + + + diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppConnection.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppConnection.java new file mode 100644 index 0000000..52b2ea0 --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppConnection.java @@ -0,0 +1,76 @@ +package org.springframework.integration.smpp; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.MessagingException; +import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.core.SubscribableChannel; +import org.springframework.integration.smpp.core.SmppConstants; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.concurrent.atomic.AtomicInteger; +/** + * Simple test, more of the SMPP API than anything, at the moment. + *

+ * Demonstrates that the {@link org.springframework.integration.smpp.session.SmppSessionFactoryBean} works, too. + * + * @author Josh Long + * @since 2.1 + */ +@ContextConfiguration("classpath:TestSmppConnection-context.xml") +@RunWith(SpringJUnit4ClassRunner.class) +public class TestSmppConnection { + + @Value("${smpp.systemId}") + String destination; + + @Autowired SubscribableChannel inboundChannel; + @Autowired MessageChannel outboundChannel; + @Autowired SubscribableChannel receiptChannel; + + Message messageOut; + + AtomicInteger count = new AtomicInteger(); + + @Before + public void setUp() { + messageOut = MessageBuilder.withPayload("This is the message") + .setHeader(SmppConstants.DST_ADDR, destination) + .setHeader(SmppConstants.SRC_ADDR, destination) + .build(); + } + + + @Test + public void testSmppConnection() throws Throwable { + MessageHandler standardInboundHandler = new MessageHandler() { + @Override + public void handleMessage(Message message) throws MessagingException { + System.out.println("Standard Inbound channel receive: " + message); + count.incrementAndGet(); + } + }; + MessageHandler receiptHandler = new MessageHandler() { + @Override + public void handleMessage(Message message) throws MessagingException { + String received = message.getPayload().toString(); + System.out.println("Outbound channel output receive receipt: " + received); + } + }; + inboundChannel.subscribe(standardInboundHandler); + receiptChannel.subscribe(receiptHandler); + + outboundChannel.send(messageOut); + + Thread.sleep(5000); + } + + +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundChannelAdapter.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundChannelAdapter.java new file mode 100644 index 0000000..8f8bfc4 --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundChannelAdapter.java @@ -0,0 +1,75 @@ +package org.springframework.integration.smpp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.bean.SMSCDeliveryReceipt; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.integration.Message; +import org.springframework.integration.MessagingException; +import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.core.SubscribableChannel; +import org.springframework.integration.smpp.core.SmppConstants; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.concurrent.atomic.AtomicInteger; + +@ContextConfiguration("classpath:TestSmppInboundChannelAdapter-context.xml") +@RunWith(SpringJUnit4ClassRunner.class) +public class TestSmppInboundChannelAdapter { + + private String smsMessageToSend = "test SMPP message being sent from this time:"+ System.currentTimeMillis()+"."; + + @Value("${test.dst.number}") String number; + + private Log log = LogFactory.getLog(getClass()); + + private AtomicInteger atomicInteger = new AtomicInteger(); + + @Value("#{outbound}") SubscribableChannel out; + + @Value("#{inbound}") SubscribableChannel in; + + @Autowired + private ApplicationContext context; + + @Before + public void before () throws Throwable { + Assert.assertNotNull(this.number); + } + + String lastReceivedSms = null ; + + @Test + public void testReceiving() throws Throwable { + + + in.subscribe(new MessageHandler() { + public void handleMessage(Message message) throws MessagingException { + lastReceivedSms =(message.getPayload().toString()); + atomicInteger.incrementAndGet(); + } + }); + + // lets send something + Message smsMsg = MessageBuilder.withPayload(this.smsMessageToSend) + .setHeader(SmppConstants.SRC_ADDR, this.number) + .setHeader(SmppConstants.DST_ADDR, this.number) + .setHeader(SmppConstants.REGISTERED_DELIVERY_MODE, SMSCDeliveryReceipt.SUCCESS) + .build(); + out.send(smsMsg); + + Thread.sleep(1000 * 10); + + Assert.assertTrue(atomicInteger.intValue()>0); + Assert.assertEquals(atomicInteger.intValue() ,1); + Assert.assertEquals(this.smsMessageToSend, lastReceivedSms); + } +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundGateway.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundGateway.java new file mode 100644 index 0000000..15dd8cc --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundGateway.java @@ -0,0 +1,88 @@ +package org.springframework.integration.smpp; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.integration.Message; +import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.core.SubscribableChannel; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.smpp.core.SmesMessageSpecification; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.concurrent.atomic.AtomicInteger; + +@ContextConfiguration("classpath:TestSmppInboundGateway-context.xml") +@RunWith(SpringJUnit4ClassRunner.class) +public class TestSmppInboundGateway { + + @Value("#{out1}") SubscribableChannel out1; + @Value("#{out2}") SubscribableChannel out2; + @Value("#{in1}") SubscribableChannel in1; + @Value("#{in2}") SubscribableChannel in2; + + // test data + String toPhone = "33333"; // todo make sure the gateway automatically 'flips' with the to/from on the reply SMS + String fromPhone = "1111"; + long now = System.currentTimeMillis(); + String smsRequest = "this is a request created at " + now; + String smsResponse = "this is a response created at " + now; + + @Value("#{outboundSession}") + ExtendedSmppSession outSession; + + @Before public void before(){ + outSession.start(); + } + + AtomicInteger count = new AtomicInteger(); + + @Test + public void testSendingAndReceivingAnSms() throws Throwable { + + // the gateway *receives* SMS messages, and then expects a reply. + // So we need to both *send* an SMS for the gateway to receive, and then *receive* the reply + // from the gateway to confirm it was sent... + + // two sends, two receives, one pair taken care of the by the gateway + // it would be ideal if we could in essence wrap this inbound gateway with an outbound gateway so that.. + + // outbound-gw: send + //// inbound-gw: receive, produces reply + //// inbound-gw: send + // outbound-gw: receive + // however atm thats not supported by the outbound-gw, it only 'replies' with the message ID of the outbound send + + // anyway.... + //1) lets send an outbound message so that our gateway has something to listen for + + SmesMessageSpecification.newSmesMessageSpecification(outSession, this.fromPhone, this.toPhone, this.smsRequest).send(); + + MessageHandler inboundMessageHandler = new AbstractReplyProducingMessageHandler() { + @Override + protected Object handleRequestMessage(Message requestMessage) { + Assert.assertEquals(requestMessage.getPayload(), smsRequest); + count.incrementAndGet(); + return MessageBuilder.withPayload( + smsResponse).copyHeadersIfAbsent( requestMessage.getHeaders()).build(); + + } + }; + this.in1.subscribe(inboundMessageHandler); + + // launch the whole thing + + Thread.sleep(1000 * 10); + + Assert.assertEquals(this.count.intValue(),1); + + + } +} + + diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundChannelAdapter.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundChannelAdapter.java new file mode 100644 index 0000000..ab8a29e --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundChannelAdapter.java @@ -0,0 +1,60 @@ +package org.springframework.integration.smpp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.bean.SMSCDeliveryReceipt; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.smpp.core.SmppConstants; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * exercises the outbound adapter. + * + * @author Josh Long + * @since 2.1 + */ +@ContextConfiguration("classpath:TestSmppOutboundChannelAdapter-context.xml") +@RunWith(SpringJUnit4ClassRunner.class) +public class TestSmppOutboundChannelAdapter { + + + private Log log = LogFactory.getLog(getClass()); + + @Value("#{session}") + private ExtendedSmppSession smppSession; + + @Value("#{outboundSms}") + private MessageChannel messageChannel; + + private String smsMessageToSend = "jSMPP is truly a convenient, and powerful API for SMPP " + + "on the Java and Spring Integration platforms (sent " + System.currentTimeMillis() + ")"; + + + @Before + public void start (){ + + + } + + @Test + public void testSendingAndReceivingASmppMessageUsingRawApi() throws Throwable { + + + log.debug( "sending a message."); + Message smsMsg = MessageBuilder.withPayload(this.smsMessageToSend) + .setHeader(SmppConstants.SRC_ADDR, "1616") + .setHeader(SmppConstants.DST_ADDR, "628176504657") + .setHeader(SmppConstants.REGISTERED_DELIVERY_MODE, SMSCDeliveryReceipt.SUCCESS) + .build(); + + this.messageChannel.send(smsMsg); + } +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundGateway.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundGateway.java new file mode 100644 index 0000000..d2516e7 --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundGateway.java @@ -0,0 +1,59 @@ +package org.springframework.integration.smpp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.bean.SMSCDeliveryReceipt; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.smpp.core.SmppConstants; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/*** + * Simple tests for the gateway which differs from the outbound adapter only in that it supports + * sending the message ID back + * + * + * @author Josh Long + * @since 2.1 + */ +@ContextConfiguration("classpath:TestSmppOutboundGateway-context.xml") +@RunWith(SpringJUnit4ClassRunner.class) +public class TestSmppOutboundGateway { + + + private MessagingTemplate messagingTemplate = new MessagingTemplate(); + private Log log = LogFactory.getLog(getClass()); + + @Value("#{session}") + private ExtendedSmppSession smppSession; + + @Value("#{outboundSms}") + private MessageChannel messageChannel; + + private String smsMessageToSend = "jSMPP is truly a convenient, and powerful API for SMPP " + + "on the Java and Spring Integration platforms (sent " + System.currentTimeMillis() + ")"; + + @Test + public void testSendingAndReceivingASmppMessageUsingRawApi() throws Throwable { + + Message smsMsg = MessageBuilder.withPayload(this.smsMessageToSend) + .setHeader(SmppConstants.SRC_ADDR, "1616") + .setHeader(SmppConstants.DST_ADDR, "628176504657") + .setHeader(SmppConstants.REGISTERED_DELIVERY_MODE, SMSCDeliveryReceipt.SUCCESS) + .build(); + + Message response = this.messagingTemplate.sendAndReceive(this.messageChannel,smsMsg); + + Assert.assertNotNull(response); + Assert.assertTrue(response.getPayload() instanceof String); + log.info("received the SMS Message ID: " + response.getPayload()); + } +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppSessionFactoryBean.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppSessionFactoryBean.java new file mode 100644 index 0000000..13f1357 --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppSessionFactoryBean.java @@ -0,0 +1,128 @@ +package org.springframework.integration.smpp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.bean.AlertNotification; +import org.jsmpp.bean.BindType; +import org.jsmpp.bean.DataSm; +import org.jsmpp.bean.DeliverSm; +import org.jsmpp.extra.ProcessRequestException; +import org.jsmpp.session.DataSmResult; +import org.jsmpp.session.MessageReceiverListener; +import org.jsmpp.session.SMPPSession; +import org.jsmpp.session.Session; +import org.jsmpp.util.AbsoluteTimeFormatter; +import org.junit.*; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.integration.smpp.session.DelegatingMessageReceiverListener; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.smpp.session.ExtendedSmppSessionAdaptingDelegate; +import org.springframework.integration.smpp.session.SmppSessionFactoryBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Field; +import java.util.Set; + +/** + * Simple test, more of the SMPP API than anything, at the moment. + *

+ * Demonstrates that the {@link org.springframework.integration.smpp.session.SmppSessionFactoryBean} works, too. + * + * @author Josh Long + * @since 2.1 + */ +@ContextConfiguration("classpath:TestSmppSessionFactoryBean-context.xml") +@RunWith(SpringJUnit4ClassRunner.class) +public class TestSmppSessionFactoryBean { + + private Log logger = LogFactory.getLog(getClass()); + + @Autowired + @Qualifier("session") + private ExtendedSmppSessionAdaptingDelegate smppSession; + + private AbsoluteTimeFormatter timeFormatter = new AbsoluteTimeFormatter(); + + private String host = "127.0.0.1"; + + private int port = 2775; + + private String systemId = "smppclient1"; + + private String password = "password"; + + @Test + public void testSmppSessionFactory() throws Throwable { + + SmppSessionFactoryBean smppSessionFactoryBean = new SmppSessionFactoryBean(); + smppSessionFactoryBean.setSystemId(this.systemId); + smppSessionFactoryBean.setPort(this.port); + smppSessionFactoryBean.setPassword(this.password); + smppSessionFactoryBean.setHost(this.host); + smppSessionFactoryBean.afterPropertiesSet(); + + ExtendedSmppSession extendedSmppSession = smppSessionFactoryBean.getObject(); + Assert.assertTrue(extendedSmppSession instanceof ExtendedSmppSessionAdaptingDelegate); + + ExtendedSmppSessionAdaptingDelegate es = (ExtendedSmppSessionAdaptingDelegate) extendedSmppSession; + Assert.assertNotNull("the factoried object should not be null", extendedSmppSession); + es.addMessageReceiverListener(new MessageReceiverListener() { + public void onAcceptDeliverSm(DeliverSm deliverSm) throws ProcessRequestException { + logger.debug("in onAcceptDeliverSm"); + } + + public void onAcceptAlertNotification(AlertNotification alertNotification) { + logger.debug("in onAcceptAlertNotification"); + } + + public DataSmResult onAcceptDataSm(DataSm dataSm, Session source) throws ProcessRequestException { + logger.debug("in onAcceptDataSm"); + return null; + } + }); + Assert.assertEquals(extendedSmppSession.getClass(), ExtendedSmppSessionAdaptingDelegate.class); + Assert.assertNotNull(es.getTargetClientSession()); + Assert.assertTrue(es.getTargetClientSession() != null); + final SMPPSession s = es.getTargetClientSession(); + + ReflectionUtils.doWithFields(ExtendedSmppSessionAdaptingDelegate.class, new ReflectionUtils.FieldCallback() { + public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { + if (field.getName().equalsIgnoreCase("messageReceiverListener")) { + field.setAccessible(true); + MessageReceiverListener messageReceiverListener = (MessageReceiverListener) field.get(s); + Assert.assertNotNull(messageReceiverListener); + Assert.assertTrue(messageReceiverListener instanceof DelegatingMessageReceiverListener); + final DelegatingMessageReceiverListener delegatingMessageReceiverListener = (DelegatingMessageReceiverListener) messageReceiverListener; + ReflectionUtils.doWithFields(DelegatingMessageReceiverListener.class, new ReflectionUtils.FieldCallback() { + public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { + if (field.getName().equals("messageReceiverListenerSet")) { + field.setAccessible(true); + @SuppressWarnings("unchecked") + Set l = (Set) field.get(delegatingMessageReceiverListener); + Assert.assertEquals(l.size(), 1); + } + } + }); + } + } + }); + } + + @Test + public void testWhetherTheBeansAlreadyStarted() throws Throwable { + + Assert.assertNotNull("session shouldn't be null", this.smppSession); + + Assert.assertTrue("the " + ExtendedSmppSession.class.getName() + " should be started if the " + + "container supports Lifecycle, otherwise, it must be manually #start'd", + (smppSession).isRunning()); + + BindType bindType = smppSession.getBindType(); + + Assert.assertNotNull("the bind type should not be null", bindType); + } +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/MockSmppSessionFactory.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/MockSmppSessionFactory.java new file mode 100644 index 0000000..859f471 --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/MockSmppSessionFactory.java @@ -0,0 +1,30 @@ +package org.springframework.integration.smpp.config.xml; + +import org.jsmpp.bean.BindType; +import org.springframework.integration.smpp.session.ExtendedSmppSession; + +import static org.easymock.EasyMock.createNiceMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; + +/** + * Mock smpp session factory for testing. + * @author Johanes Soetanto + * @since 2.2 + */ +public class MockSmppSessionFactory { + + public static ExtendedSmppSession getOutSmppSession() { + ExtendedSmppSession mock = createNiceMock(ExtendedSmppSession.class); + expect(mock.getBindType()).andReturn(BindType.BIND_TX).anyTimes(); + replay(mock); + return mock; + } + + public static ExtendedSmppSession getInSmppSession() { + ExtendedSmppSession mock = createNiceMock(ExtendedSmppSession.class); + expect(mock.getBindType()).andReturn(BindType.BIND_RX).anyTimes(); + replay(mock); + return mock; + } +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.java new file mode 100644 index 0000000..b786f7e --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.java @@ -0,0 +1,66 @@ +/* +uytea * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package org.springframework.integration.smpp.config.xml; + +import org.junit.After; +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.smpp.inbound.SmppInboundChannelAdapter; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.test.util.TestUtils; + +import static org.junit.Assert.*; + + +/** + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppInboundChannelAdapterParserTests { + + private ConfigurableApplicationContext context; + + private SmppInboundChannelAdapter consumer; + + @Test + public void testInboundChannelAdapterParser() throws Exception { + + setUp("SmppInboundChannelAdapterParserTests.xml", getClass(), "smppInboundChannelAdapter"); + + final AbstractMessageChannel outputChannel = TestUtils.getPropertyValue(this.consumer, "channel", AbstractMessageChannel.class); + assertEquals("out", outputChannel.getComponentName()); + + ExtendedSmppSession session = TestUtils.getPropertyValue(consumer, "smppSession", ExtendedSmppSession.class); + assertNotNull(session); + + boolean autoStartup = TestUtils.getPropertyValue(consumer, "autoStartup" , Boolean.class); + assertTrue(autoStartup); + } + + @After + public void tearDown(){ + if(context != null){ + context.close(); + } + } + + public void setUp(String name, Class cls, String consumerId){ + context = new ClassPathXmlApplicationContext(name, cls); + consumer = this.context.getBean(consumerId, SmppInboundChannelAdapter.class); + } + +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.java new file mode 100644 index 0000000..9ac2ac5 --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.jsmpp.bean.TypeOfNumber; +import org.junit.After; +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.mapping.InboundMessageMapper; +import org.springframework.integration.mapping.OutboundMessageMapper; +import org.springframework.integration.smpp.inbound.SmppInboundGateway; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.test.util.TestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + + +/** + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppInboundGatewayParserTests { + + private ConfigurableApplicationContext context; + + private SmppInboundGateway gateway; + + @Test + public void testRetrievingInboundGatewayParser() throws Exception { + setUp("SmppInboundGatewayParserTests.xml", getClass(), "smppInboundGateway"); + + // reply timeout + long requestTimeout = TestUtils.getPropertyValue(gateway, "messagingTemplate.sendTimeout", Long.class); + assertEquals(10000, requestTimeout); + + // request timeout + long replyTimeout = TestUtils.getPropertyValue(gateway, "messagingTemplate.receiveTimeout", Long.class); + assertEquals(5000, replyTimeout); + + ExtendedSmppSession session = TestUtils.getPropertyValue(gateway, "smppSession", ExtendedSmppSession.class); + assertNotNull(session); + System.out.println("Session: "+session); + + TypeOfNumber ton = TestUtils.getPropertyValue(gateway, "defaultSourceAddressTypeOfNumber", TypeOfNumber.class); + assertEquals(ton, TypeOfNumber.INTERNATIONAL); + + String sourceAddress = TestUtils.getPropertyValue(gateway, "defaultSourceAddress", String.class); + assertEquals("123456789", sourceAddress); + + // channels + AbstractMessageChannel requestChannel = TestUtils.getPropertyValue(gateway, "requestChannel", AbstractMessageChannel.class); + assertEquals("requestChannel", requestChannel.getComponentName()); + AbstractMessageChannel outputChannel = TestUtils.getPropertyValue(gateway, "replyChannel", AbstractMessageChannel.class); + assertEquals("replyChannel", outputChannel.getComponentName()); + AbstractMessageChannel errorChannel = TestUtils.getPropertyValue(gateway, "errorChannel", AbstractMessageChannel.class); + assertEquals("errorChannel", errorChannel.getComponentName()); + + // mappers + InboundMessageMapper inboundMessageMapper = TestUtils.getPropertyValue(gateway, "requestMapper", InboundMessageMapper.class); + assertNotNull(inboundMessageMapper); + OutboundMessageMapper outboundMessageMapper = TestUtils.getPropertyValue(gateway, "messageConverter.outboundMessageMapper", OutboundMessageMapper.class); + assertNotNull(outboundMessageMapper); + + } + + + + @After + public void tearDown() { + if (context != null) { + context.close(); + } + } + + public void setUp(String name, Class cls, String gatewayId) { + context = new ClassPathXmlApplicationContext(name, cls); + gateway = this.context.getBean(gatewayId, SmppInboundGateway.class); + } + +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.java new file mode 100644 index 0000000..9e54245 --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.jsmpp.bean.TypeOfNumber; +import org.jsmpp.util.TimeFormatter; +import org.junit.After; +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.smpp.core.SmppConstants; +import org.springframework.integration.smpp.outbound.SmppOutboundChannelAdapter; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.test.util.TestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + + +/** + * + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppOutboundChannelAdapterParserTests { + + private ConfigurableApplicationContext context; + + private EventDrivenConsumer consumer; + + @Test + public void testRetrievingOutboundChannelAdapterParser() throws Exception { + setUp("SmppOutboundChannelAdapterParserTests.xml", getClass()); + + + final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(this.consumer, "inputChannel", AbstractMessageChannel.class); + assertEquals("target", inputChannel.getComponentName()); + + final SmppOutboundChannelAdapter gateway = TestUtils.getPropertyValue(this.consumer, "handler", SmppOutboundChannelAdapter.class); + + ExtendedSmppSession session = TestUtils.getPropertyValue(gateway, "smppSession", ExtendedSmppSession.class); + assertNotNull(session); + + TypeOfNumber ton = TestUtils.getPropertyValue(gateway, "defaultSourceAddressTypeOfNumber", TypeOfNumber.class); + assertEquals(ton, TypeOfNumber.SUBSCRIBER_NUMBER); + + String sourceAddress = TestUtils.getPropertyValue(gateway, "defaultSourceAddress", String.class); + assertEquals("12345", sourceAddress); + + // this is not set, should be default value + TimeFormatter timeFormatter = TestUtils.getPropertyValue(gateway, "timeFormatter", TimeFormatter.class); + assertNotNull(timeFormatter); + + // I send message + Message message = MessageBuilder.withPayload("Yuhuu !!! i am connected using Spring Integration namespace") + .setHeader(SmppConstants.SRC_ADDR, "pavel") + .setHeader(SmppConstants.DST_ADDR, "pavel") + .build(); + + MessagingTemplate template = context.getBean("messagingTemplate", MessagingTemplate.class); + template.send("target", message); + } + + @After + public void tearDown(){ + if(context != null){ + context.close(); + } + } + + public void setUp(String name, Class cls){ + context = new ClassPathXmlApplicationContext(name, cls); + consumer = this.context.getBean("smppOutboundChannelAdapter", EventDrivenConsumer.class); + } + +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.java new file mode 100644 index 0000000..980be5f --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.jsmpp.bean.TypeOfNumber; +import org.jsmpp.util.TimeFormatter; +import org.junit.After; +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.smpp.outbound.SmppOutboundGateway; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.test.util.TestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + + +/** + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppOutboundGatewayParserTests { + + private ConfigurableApplicationContext context; + + private EventDrivenConsumer consumer; + + @Test + public void testRetrievingOutboundGatewayParser() throws Exception { + setUp("SmppOutboundGatewayParserTests.xml", getClass(), "smppOutboundGateway"); + + + final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(this.consumer, "inputChannel", AbstractMessageChannel.class); + assertEquals("in", inputChannel.getComponentName()); + + final SmppOutboundGateway gateway = TestUtils.getPropertyValue(this.consumer, "handler", SmppOutboundGateway.class); + + long sendTimeout = TestUtils.getPropertyValue(gateway, "messagingTemplate.sendTimeout", Long.class); + assertEquals(100, sendTimeout); + + ExtendedSmppSession session = TestUtils.getPropertyValue(gateway, "smppSession", ExtendedSmppSession.class); + assertNotNull(session); + + TypeOfNumber ton = TestUtils.getPropertyValue(gateway, "defaultSourceAddressTypeOfNumber", TypeOfNumber.class); + assertEquals(ton, TypeOfNumber.NETWORK_SPECIFIC); + + String sourceAddress = TestUtils.getPropertyValue(gateway, "defaultSourceAddress", String.class); + assertEquals("123456789", sourceAddress); + + int order = TestUtils.getPropertyValue(gateway, "order", Integer.class); + assertEquals(17, order); + + AbstractMessageChannel outputChannel = TestUtils.getPropertyValue(gateway, "outputChannel", AbstractMessageChannel.class); + assertEquals("out", outputChannel.getComponentName()); + + // this is not set, should be default value + TimeFormatter timeFormatter = TestUtils.getPropertyValue(gateway, "timeFormatter", TimeFormatter.class); + assertNotNull(timeFormatter); + } + + + + @After + public void tearDown() { + if (context != null) { + context.close(); + } + } + + public void setUp(String name, Class cls, String gatewayId) { + context = new ClassPathXmlApplicationContext(name, cls); + consumer = this.context.getBean(gatewayId, EventDrivenConsumer.class); + } + +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/util/TestCurrentExecutingMethodAdvice.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/util/TestCurrentExecutingMethodAdvice.java new file mode 100644 index 0000000..7b346cb --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/util/TestCurrentExecutingMethodAdvice.java @@ -0,0 +1,40 @@ +package org.springframework.integration.smpp.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.aop.framework.ProxyFactoryBean; + +/** + * @author Josh Long + * @since 2.1 + */ +public class TestCurrentExecutingMethodAdvice { + static String currentMethodName() { + return CurrentExecutingMethodHolder.getCurrentlyExecutingMethod().getName(); + } + + static public class TestClassWithAMethod { + public TestClassWithAMethod() { + } + + private Log l = LogFactory.getLog(getClass()); + + public void testMe() throws Throwable { + Assert.assertEquals("testMe", currentMethodName()); + l.debug("The currently executing method name is " + currentMethodName()); + } + } + + @Test + public void testLoggingTheCurrentlyExecutingMethodName() throws Throwable { + ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); + proxyFactoryBean.setProxyTargetClass(true); + proxyFactoryBean.addAdvice(new CurrentMethodExposingMethodInterceptor()); + proxyFactoryBean.setTarget(new TestClassWithAMethod()); + + TestClassWithAMethod testClassWithAMethod = (TestClassWithAMethod) proxyFactoryBean.getObject(); + testClassWithAMethod.testMe(); + } +} diff --git a/spring-integration-smpp/src/test/resources/TestSmppConnection-context.xml b/spring-integration-smpp/src/test/resources/TestSmppConnection-context.xml new file mode 100644 index 0000000..6211653 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/TestSmppConnection-context.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/TestSmppInboundChannelAdapter-context.xml b/spring-integration-smpp/src/test/resources/TestSmppInboundChannelAdapter-context.xml new file mode 100644 index 0000000..5e4cbac --- /dev/null +++ b/spring-integration-smpp/src/test/resources/TestSmppInboundChannelAdapter-context.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/TestSmppInboundGateway-context.xml b/spring-integration-smpp/src/test/resources/TestSmppInboundGateway-context.xml new file mode 100644 index 0000000..393c146 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/TestSmppInboundGateway-context.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/TestSmppOutboundChannelAdapter-context.xml b/spring-integration-smpp/src/test/resources/TestSmppOutboundChannelAdapter-context.xml new file mode 100644 index 0000000..9377679 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/TestSmppOutboundChannelAdapter-context.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/TestSmppOutboundGateway-context.xml b/spring-integration-smpp/src/test/resources/TestSmppOutboundGateway-context.xml new file mode 100644 index 0000000..cf52360 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/TestSmppOutboundGateway-context.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/TestSmppSessionFactoryBean-context.xml b/spring-integration-smpp/src/test/resources/TestSmppSessionFactoryBean-context.xml new file mode 100644 index 0000000..7672b5c --- /dev/null +++ b/spring-integration-smpp/src/test/resources/TestSmppSessionFactoryBean-context.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/log4j.properties b/spring-integration-smpp/src/test/resources/log4j.properties new file mode 100644 index 0000000..67e6463 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/log4j.properties @@ -0,0 +1,8 @@ +log4j.rootCategory=WARN, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss.SSS} %-5p [%t][%c] %m%n + +log4j.category.org.springframework.integration=WARN +log4j.category.org.springframework.integration.smpp=INFO diff --git a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.xml b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.xml new file mode 100644 index 0000000..92e4c59 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.xml b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.xml new file mode 100644 index 0000000..4d52410 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.xml b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.xml new file mode 100644 index 0000000..7c736f8 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.xml b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.xml new file mode 100644 index 0000000..1c68b0b --- /dev/null +++ b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/smpp.properties b/spring-integration-smpp/src/test/resources/smpp.properties new file mode 100644 index 0000000..1628ffe --- /dev/null +++ b/spring-integration-smpp/src/test/resources/smpp.properties @@ -0,0 +1,7 @@ +smpp.host=127.0.0.1 +smpp.systemId=pavel +smpp.password=wpsd +smpp.port=2775 + + +test.dst.number=123456 \ No newline at end of file