Merge pull request #16 from otnateos/INTEXT-30

* otnateos-INTEXT-30:
  INTEXT-30 - Cleanup
  Change group id
  INTEXT-30 - Add SMPP Support - update to version 2.2.0.BUILD-SNAPSHOT
  INTEXT-30 - Add SMPP Support - migrate old sandbox project - add schema for channel adapters and gateways - change some package structure based on spring integration module package structure
This commit is contained in:
Gunnar Hillert
2013-01-09 16:37:01 -05:00
75 changed files with 5198 additions and 0 deletions

View File

@@ -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

View File

@@ -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'
}

View File

@@ -0,0 +1 @@
version=2.2.0.BUILD-SNAPSHOT

Binary file not shown.

View File

@@ -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

164
spring-integration-smpp/gradlew vendored Executable file
View File

@@ -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 "$@"

90
spring-integration-smpp/gradlew.bat vendored Normal file
View File

@@ -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

View File

@@ -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'
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
<html>
<body>
This document is the API specification for Spring Integration Smpp Module
<hr/>
<div id="overviewBody">
<p>
For further API reference and developer documentation, see the
<a href="http://static.springsource.org/spring-integration/reference" target="_top">Spring
Integration reference documentation</a>.
That documentation contains more detailed, developer-targeted
descriptions, with conceptual overviews, definitions of terms,
workarounds, and working code examples.
</p>
<p>
If you are interested in commercial training, consultancy, and
support for Spring Integration, please visit <a href="http://www.springsource.com" target="_top">
http://www.springsource.com</a>
</p>
</div>
</body>
</html>

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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();
}
}

View File

@@ -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");
}
}

View File

@@ -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());
}
}

View File

@@ -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();
}
}

View File

@@ -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";
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,4 @@
/**
* Provides parser classes to provide Xml namespace support for the Smpp components.
*/
package org.springframework.integration.smpp.config.xml;

View File

@@ -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 <em>receipt</em>, 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;
}

View File

@@ -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 <em>tiny</em> 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<String> 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.<NumberingPlanIndicator>valueIfHeaderExists(DST_NPI, msg));
spec.setSourceAddressNumberingPlanIndicator(SmesMessageSpecification.<NumberingPlanIndicator>valueIfHeaderExists(SRC_NPI, msg));
spec.setDestinationAddressTypeOfNumber(SmesMessageSpecification.<TypeOfNumber>valueIfHeaderExists(DST_TON, msg));
spec.setSourceAddressTypeOfNumber(SmesMessageSpecification.<TypeOfNumber>valueIfHeaderExists(SRC_TON, msg));
spec.setServiceType(SmesMessageSpecification.<String>valueIfHeaderExists(SERVICE_TYPE, msg));
spec.setEsmClass(SmesMessageSpecification.esmClassFromHeader(msg));
spec.setScheduleDeliveryTime(SmesMessageSpecification.<Date>valueIfHeaderExists(SCHEDULED_DELIVERY_TIME, msg));
spec.setDataCoding(SmesMessageSpecification. dataCodingFromHeader( msg));
spec.setValidityPeriod(SmesMessageSpecification.<String>valueIfHeaderExists(VALIDITY_PERIOD, msg));
// byte landmine. autoboxing causes havoc with <em>null</em> bytes.
Byte priorityFlag1 = SmesMessageSpecification.<Byte>valueIfHeaderExists(PRIORITY_FLAG, msg);
if (priorityFlag1 != null)
spec.setPriorityFlag(priorityFlag1);
Byte smDefaultMsgId1 = SmesMessageSpecification.<Byte>valueIfHeaderExists(SM_DEFAULT_MSG_ID, msg);
if (smDefaultMsgId1 != null)
spec.setSmDefaultMsgId(smDefaultMsgId1);
Byte replaceIfPresentFlag1 = SmesMessageSpecification.<Byte>valueIfHeaderExists(REPLACE_IF_PRESENT_FLAG, msg);
if (replaceIfPresentFlag1 != null)
spec.setReplaceIfPresentFlag(replaceIfPresentFlag1);
Byte protocolId1 = SmesMessageSpecification.<Byte>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> 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.
* <p/>
* 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 <em>and</eM> 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
* <em>validity period</em> 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.
* <p/>
* 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.
* <p/>
* That information came from <a href="http://www.nowsms.com/smpp-information">the NowSMS website.</a>.
*
* @param v the period of validity. There are specific formats for this, however this method provides no validation.
* <p/>
* 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 <em>quite</em> 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
* <p/>
* 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.
* <p/>
* 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<String> marshalling = new HashSet<String>();
Set<String> headers = new HashSet<String>();
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]));
}
}
*/

View File

@@ -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";
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,4 @@
/**
* Provides core classes of the Smpp module.
*/
package org.springframework.integration.smpp.core;

View File

@@ -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 <em>short message service center</em> (SMSC).
*
* @author Josh Long
* @since 2.1
* <p/>
* 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";
}
}

View File

@@ -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";
}
}

View File

@@ -0,0 +1,4 @@
/**
* Provides inbound Spring Integration Smpp components.
*/
package org.springframework.integration.smpp.inbound;

View File

@@ -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 <em>all</em> the tedium of converting and validating the configuration.
* <p/>
* This adapter supports <em>mobile terminated (MT)</em> 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";
}
}

View File

@@ -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.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
*
* @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";
}
}

View File

@@ -0,0 +1,4 @@
/**
* Provides Spring Integration components for doing outbound operations.
*/
package org.springframework.integration.smpp.outbound;

View File

@@ -0,0 +1,4 @@
/**
* Root package of the Smpp Module.
*/
package org.springframework.integration.smpp;

View File

@@ -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 <em>before</em> connection.
* <p/>
* 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<MessageReceiverListener> messageReceiverListenerSet =
new CopyOnWriteArraySet<MessageReceiverListener>();
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);
}
}

View File

@@ -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:
* <p/>
* <ol><li>supports registration of multiple {@link org.jsmpp.session.MessageReceiverListener}s</li></ol>
*
* @author Josh Long
* @since 2.1
*/
public interface ExtendedSmppSession extends ClientSession {
/**
* a {@link MessageReceiverListener} implementation to be added to the set of existing listeners.
* <p/>
* 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() ;
}

View File

@@ -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!");
}
}

View File

@@ -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}.
* <p/>
* The {@link SMPPSession } represents a connection to a SMSC, through which SMS messages are sent and received.
* <p/>
* Here is a breakdown of the supported parameters on this factory bean:
* <p/>
* 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
* <p/>
* 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<ExtendedSmppSession>, 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<MessageReceiverListener> messageReceiverListeners = new HashSet<MessageReceiverListener>();
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 <em>requires</em> 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 <em>listen</em> 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<MessageReceiverListener>(Arrays.asList(listeners)));
}
public void setMessageReceiverListeners(Set<MessageReceiverListener> 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}
* <p/>
* 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);
}
}
}
*/

View File

@@ -0,0 +1,4 @@
/**
* Provides various classes used for Spring Integration Smpp session.
*/
package org.springframework.integration.smpp.session;

View File

@@ -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<Method> methodThreadLocal = new NamedThreadLocal<Method>("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();
}
}

View File

@@ -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.
* <p/>
* 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();
}
}
}

View File

@@ -0,0 +1,4 @@
/**
* Provides various util classes used across Spring Integration Smpp Components.
*/
package org.springframework.integration.smpp.util;

View File

@@ -0,0 +1 @@
http\://www.springframework.org/schema/integration/smpp=org.springframework.integration.smpp.config.xml.SmppNamespaceHandler

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,439 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.springframework.org/schema/integration/smpp"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:integration="http://www.springframework.org/schema/integration"
targetNamespace="http://www.springframework.org/schema/integration/smpp"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans" />
<xsd:import namespace="http://www.springframework.org/schema/tool" />
<xsd:import namespace="http://www.springframework.org/schema/integration"
schemaLocation="http://www.springframework.org/schema/integration/spring-integration-2.2.xsd" />
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the configuration elements for the Spring Integration
Smpp Adapter.
]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="inbound-channel-adapter">
<xsd:annotation>
<xsd:documentation>
The definition for the Spring Integration Smpp
Inbound Channel Adapter.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element minOccurs="0" maxOccurs="1" ref="session"/>
</xsd:choice>
<xsd:attributeGroup ref="coreSmppComponentAttributes"/>
<xsd:attribute name="auto-startup" default="true" use="optional">
<xsd:annotation>
<xsd:documentation>
Flag to indicate that the component should start automatically
on startup (default true).
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:boolean xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="channel" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Channel which the sms will be put in, whey they come from the SMSC.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.core.MessageChannel" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="inbound-gateway">
<xsd:annotation>
<xsd:documentation>
Defines the Spring Integration Smpp Inbound Gateway
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="smppGatewayType">
<xsd:attribute name="error-channel" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.core.MessageChannel" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="request-timeout" type="xsd:string" />
<xsd:attribute name="request-mapper" use="optional">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.mapping.InboundMessageMapper" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="reply-mapper" use="optional">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.mapping.OutboundMessageMapper" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="outbound-channel-adapter">
<xsd:annotation>
<xsd:documentation>
Defines an Outbound Channel Adapter.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element minOccurs="0" maxOccurs="1" ref="session"/>
</xsd:choice>
<xsd:attributeGroup ref="coreSmppComponentAttributes"/>
<xsd:attribute name="channel" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
Channel from which messages will be output.
When a message is sent to this channel it will
cause the query
to be executed.
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.MessageChannel" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="source-address" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
Source address will be used as sender for SMPP
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="source-ton">
<xsd:annotation>
<xsd:documentation>
The default source address Type of Number. Default is UNKNOWN.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="tonEnumeration xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="time-formatter">
<xsd:annotation>
<xsd:documentation>
Reference to jsmpp time formatter
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.jsmpp.util.TimeFormatter"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="outbound-gateway">
<xsd:annotation>
<xsd:documentation>
Defines the Spring Integration Smpp Outbound Gateway
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="smppGatewayType">
<xsd:attribute name="time-formatter">
<xsd:annotation>
<xsd:documentation>
Reference to jsmpp time formatter
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.jsmpp.util.TimeFormatter"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order">
<xsd:annotation>
<xsd:documentation>
Specifies the order for invocation when this endpoint is connected as a
subscriber to a SubscribableChannel.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="session">
<xsd:annotation>
<xsd:documentation>
Defines reference to smpp session
</xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.springframework.integration.smpp.session.ExtendedSmppSession"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element minOccurs="0" maxOccurs="1" ref="beans:bean"/>
</xsd:choice>
<xsd:attribute name="id" type="xsd:string" use="optional"/>
<xsd:attribute name="ref">
<xsd:annotation>
<xsd:documentation>
Reference to extended smpp session
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.smpp.session.ExtendedSmppSession"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="smppSessionType">
<xsd:annotation>
<xsd:documentation>
Defines the Spring Integration Smpp Session
</xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.springframework.integration.smpp.session.ExtendedSmppSession"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:attribute name="id" type="xsd:string" use="optional"/>
<xsd:attribute name="bind-type">
<xsd:annotation>
<xsd:documentation>Type of SMPP connection bind.</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="typeOfBindEnumeration xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="enquire-link-timer" type="xsd:string">
<xsd:annotation>
<xsd:documentation>Set enquire link timer (in milliseconds).</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="transaction-timer" type="xsd:string">
<xsd:annotation>
<xsd:documentation>Set transaction timer (in milliseconds).</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="use-ssl" type="xsd:boolean"/>
<xsd:attribute name="host" type="xsd:string">
<xsd:annotation>
<xsd:documentation>Host to connect (default 127.0.0.1)</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="address-range" type="xsd:string">
<xsd:annotation>
<xsd:documentation>Address range we are listening to</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="timeout" type="xsd:long">
<xsd:annotation>
<xsd:documentation>Connection timeout (default 60000ms / 1 minute)</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="port" type="xsd:int">
<xsd:annotation>
<xsd:documentation>Port to connect (default 2775)</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="system-id" type="xsd:string"/>
<xsd:attribute name="password" type="xsd:string"/>
<xsd:attribute name="system-type" type="xsd:string"/>
<xsd:attribute name="addr-ton">
<xsd:annotation>
<xsd:documentation>The address Type of Number. Default is UNKNOWN.</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="tonEnumeration xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="addr-npi">
<xsd:annotation>
<xsd:documentation>The address Numbering Plan Indicator. Default is UNKNOWN</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="npiEnumeration xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
<xsd:simpleType name="tonEnumeration">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="UNKNOWN"/>
<xsd:enumeration value="INTERNATIONAL"/>
<xsd:enumeration value="NATIONAL"/>
<xsd:enumeration value="NETWORK_SPECIFIC"/>
<xsd:enumeration value="SUBSCRIBER_NUMBER"/>
<xsd:enumeration value="ALPHANUMERIC"/>
<xsd:enumeration value="ABBREVIATED"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="npiEnumeration">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="UNKNOWN"/>
<xsd:enumeration value="ISDN"/>
<xsd:enumeration value="DATA"/>
<xsd:enumeration value="TELEX"/>
<xsd:enumeration value="LAND_MOBILE"/>
<xsd:enumeration value="NATIONAL"/>
<xsd:enumeration value="PRIVATE"/>
<xsd:enumeration value="ERMES"/>
<xsd:enumeration value="INTERNET"/>
<xsd:enumeration value="WAP"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="typeOfBindEnumeration">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="BIND_TX">
<xsd:annotation>
<xsd:documentation>
Bind as Transmitter (Sending Only)
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="BIND_RX">
<xsd:annotation>
<xsd:documentation>
Bind as Receiver (Receive Only)
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="BIND_TRX">
<xsd:annotation>
<xsd:documentation>
Bind as Transceiver (Sending and Receive)
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:attributeGroup name="coreSmppComponentAttributes">
<xsd:attribute name="id" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
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'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="smpp-session-ref">
<xsd:annotation>
<xsd:documentation>
Reference to extended smpp session
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.smpp.session.ExtendedSmppSession"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:complexType name="smppGatewayType">
<xsd:annotation>
<xsd:documentation>
Defines common configuration for gateway adapters.
</xsd:documentation>
</xsd:annotation>
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element minOccurs="0" maxOccurs="1" ref="session"/>
</xsd:choice>
<xsd:attributeGroup ref="coreSmppComponentAttributes"/>
<xsd:attribute name="reply-channel" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.MessageChannel" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="reply-timeout" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Allows you to specify how long this gateway will wait for
the reply message to be sent successfully before throwing
an exception. Keep in mind that when sending to a
DirectChannel, the invocation will occur in the sender's thread
so the failing of the send operation may be caused by other
components further downstream. By default the Gateway will
wait indefinitely. The value is specified in milliseconds.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="request-channel" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
The receiving Message Channel of this endpoint.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.core.MessageChannel" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="source-address" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
Source address will be used as sender for SMPP
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="source-ton">
<xsd:annotation>
<xsd:documentation>
The default source address Type of Number. Default is UNKNOWN.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="tonEnumeration xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<appendix xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="history"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xi="http://www.w3.org/2001/XInclude">
<title>Change History</title>
<table>
<thead>
<td>Release</td>
<td>Date</td>
<td>Changes</td>
</thead>
<tbody>
<td>2.2.0</td>
<td>2012.12.14</td>
<td>Initial release migrated from Spring sandbox</td>
</tbody>
</table>
</appendix>

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook" version="5.0"
xml:id="spring-integration-reference" xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:xlink="http://www.w3.org/1999/xlink">
<bookinfo>
<title>Spring Integration Smpp Adapter</title>
<titleabbrev>Smpp Adapter ${version}</titleabbrev>
<productname>Spring Integration</productname>
<releaseinfo>${version}</releaseinfo>
<!-- TODO: this isn't showing up. -->
<mediaobject>
<imageobject role="fo">
<imagedata fileref="images/logo.png" format="PNG" align="center" />
</imageobject>
<imageobject role="html">
<imagedata fileref="images/logo.png" format="PNG" align="center" />
</imageobject>
</mediaobject>
<!-- END TODO -->
<authorgroup>
<author><firstname>Josh Long</firstname></author>
<author><firstname>Johanes Soetanto</firstname></author>
</authorgroup>
<legalnotice>
<para>© SpringSource Inc., 2012</para>
</legalnotice>
</bookinfo>
<toc></toc>
<part id="whats-new-part">
<title>What's new?</title>
<partintro id="spring-integration-intro">
<para>
If you are interested in the changes and features, that were introduced in
earlier versions, please take a look at chapter:
<xref linkend="history" />
</para>
</partintro>
<xi:include href="./whats-new.xml" />
</part>
<part id="spring-integration-adapters">
<title>Integration Adapters</title>
<partintro id="spring-integration-adapters">
<para>
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.
</para>
</partintro>
<xi:include href="./smpp.xml" />
</part>
<part id="spring-integration-appendices">
<title>Appendices</title>
<partintro id="spring-integration-adapters">
<para>Advanced Topics and Additional Resources</para>
</partintro>
<xi:include href="./history.xml" />
</part>
</book>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<appendix xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="resources"
xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Additional Resources</title>
<section id="resources-home">
<title>Spring Integration Home</title>
<para>
The definitive source of information about Spring Integration is the
<ulink url="http://www.springsource.org/spring-integration">Spring Integration Home</ulink> at
<ulink url="http://www.springsource.org">http://www.springsource.org</ulink>. 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.
</para>
</section>
<section id="smpp-home">
<title>SMPP Home</title>
<para>
You can get more information on SMPP from <ulink url="http://en.wikipedia.org/wiki/Short_Message_Peer-to-Peer"/>
</para>
<para>
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 <ulink url="http://code.google.com/p/jsmpp/"/>
</para>
<para>
You can get SMSC simulator from <ulink url="http://opensmpp.logica.com/CommonPart/Introduction/Introduction.htm#simulator"/>
</para>
</section>
</appendix>

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="smpp"
xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Smpp Adapter</title>
<para>
The Spring Integration Smpp Adapter provides...
</para>
<itemizedlist>
<listitem>
<para><emphasis><link linkend='smpp-outbound-channel-adapter'>Outbound Channel adapter</link></emphasis></para>
</listitem>
<listitem>
<para><emphasis><link linkend='smpp-outbound-gateway'>Outbound Gateway</link></emphasis></para>
</listitem>
<listitem>
<para><emphasis><link linkend='smpp-inbound-channel-adapter'>Inbound Channel Adapter</link></emphasis></para>
</listitem>
<listitem>
<para><emphasis><link linkend='smpp-inbound-gateway'>Inbound Gateway</link></emphasis></para>
</listitem>
</itemizedlist>
<para>
To use Spring Integration adapter for SMPP, you have to import the XML namespace. For example, you can
have following XML:
</para>
<programlisting language="xml">
<![CDATA[
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-smpp="http://www.springframework.org/schema/integration/smpp"
xsi:schemaLocation="http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/smpp
http://www.springframework.org/schema/integration/smpp/spring-integration-smpp.xsd
">
</beans>
]]>
</programlisting>
<para>
Meanwhile, you have to define your SMSC server information. For example you can define
server as following:
</para>
<programlisting language="xml">
<![CDATA[
//TODO
]]>
</programlisting>
//TODO: more documentation
<section id="smpp-outbound-channel-adapter">
<title>Outbound Channel Adapter</title>
<para>
Outbound channel adapter is to send sms into SMSC from channels in Spring Integration.
</para>
</section>
<section id="smpp-inbound-channel-adapter">
<title>Inbound Channel Adapter</title>
<para>
Inbound channel adapter is used to receive sms from SMSC into Spring Integration channel.
</para>
</section>
<section id="smpp-outbound-gateway">
<title>Outbound Gateway</title>
<para>
Outbound gateway is similar to outbound channel adapter except that it can also be used to get
a result on the <emphasis>reply channel</emphasis> after sending.
</para>
</section>
<section id="smpp-inbound-gateway">
<title>Inbound Gateway</title>
<para>
Inbound gateway is similar to inbound channel adapter except that it can also be used to return
a result on the <emphasis>reply channel</emphasis> after receiving.
</para>
</section>
</chapter>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="whats-new"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:xlink="http://www.w3.org/1999/xlink">
<title>What's new?</title>
<para>
The Spring Integration adapter for SMPP includes two adapters and two gateways:
</para>
<itemizedList>
<listItem>
Inbound Channel Adapter to receive sms from SMSC.
</listItem>
<listItem>
Outbound Channel Adapter to send sms through SMSC.
</listItem>
<listItem>
Inbound Gateway to receive sms from SMSC.
</listItem>
<listItem>
Outbound Gateway to send through SMSC.
</listItem>
</itemizedList>
</chapter>

View File

@@ -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.
* <p/>
* 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<String> 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);
}
}

View File

@@ -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<String> 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<String> 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);
}
}

View File

@@ -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<String> 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());
}
}

View File

@@ -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.
* <p/>
* 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<MessageReceiverListener> l = (Set<MessageReceiverListener>) 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<String> 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:integration="http://www.springframework.org/schema/integration"
xmlns:smpp="http://www.springframework.org/schema/integration/smpp"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/smpp http://www.springframework.org/schema/integration/smpp/spring-integration-smpp.xsd">
<context:property-placeholder location="smpp.properties" />
<integration:channel id="inboundChannel" />
<integration:channel id="outboundChannel" />
<integration:channel id="receiptChannel" />
<bean id="session"
class="org.springframework.integration.smpp.session.SmppSessionFactoryBean">
<property name="host" value="${smpp.host}" />
<property name="port" value="${smpp.port}" />
<property name="password" value="${smpp.password}" />
<property name="systemId" value="${smpp.systemId}" />
<property name="bindType" value="BIND_RX" />
<property name="autoStartup" value="true" />
<property name="addressRange" value="${smpp.systemId}" />
</bean>
<bean id="session2"
class="org.springframework.integration.smpp.session.SmppSessionFactoryBean">
<property name="host" value="${smpp.host}" />
<property name="port" value="${smpp.port}" />
<property name="password" value="${smpp.password}" />
<property name="systemId" value="${smpp.systemId}" />
<property name="bindType" value="BIND_TX" />
<property name="autoStartup" value="true" />
</bean>
<smpp:inbound-channel-adapter channel="inboundChannel"
smpp-session-ref="session" />
<smpp:outbound-gateway request-channel="outboundChannel"
reply-channel="receiptChannel" smpp-session-ref="session2" />
</beans>

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="smpp.properties" />
<context:annotation-config />
<!-- SENDS SMSs to a specific number -->
<bean
class="org.springframework.integration.smpp.session.SmppSessionFactoryBean"
id="outboundSession">
<property name="host" value="${smpp.host}" />
<property name="port" value="${smpp.port}" />
<property name="bindType" value="BIND_TX" />
<property name="password" value="${smpp.password}" />
<property name="addressRange" value="${test.dst.number}" />
<property name="systemId" value="${smpp.systemId}" />
</bean>
<!-- RECEIVES SMSs from a specific number -->
<bean
class="org.springframework.integration.smpp.session.SmppSessionFactoryBean"
id="inboundSession">
<property name="host" value="${smpp.host}" />
<property name="port" value="${smpp.port}" />
<property name="bindType" value="BIND_RX" />
<property name="password" value="${smpp.password}" />
<property name="addressRange" value="${test.dst.number}" />
<!-- this says: 'i only want to receive SMS at this addy, though i can
also specify a range. It is, however, optional. This is particularly useful
as a consumer, however. -->
<property name="systemId" value="${smpp.systemId}" />
</bean>
<bean class="org.springframework.integration.smpp.inbound.SmppInboundChannelAdapter"
id="smppInboundChannelAdapter">
<property name="smppSession" ref="inboundSession" />
<property name="channel" ref="inbound" />
</bean>
<bean
class="org.springframework.integration.smpp.outbound.SmppOutboundChannelAdapter"
id="smppOutboundChannelAdapter">
<property name="smppSession" ref="outboundSession" />
</bean>
<int:outbound-channel-adapter ref="smppOutboundChannelAdapter"
channel="outbound" />
<int:channel id="outbound" />
<int:channel id="inbound" />
</beans>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="smpp.properties" />
<context:annotation-config />
<!-- SENDS SMSs to a specific number -->
<bean
class="org.springframework.integration.smpp.session.SmppSessionFactoryBean"
id="outboundSession">
<property name="host" value="${smpp.host}" />
<property name="port" value="${smpp.port}" />
<property name="password" value="${smpp.password}" />
<property name="addressRange" value="${test.dst.number}" />
<property name="systemId" value="${smpp.systemId}" />
</bean>
<!-- RECEIVES SMSs from a specific number -->
<bean
class="org.springframework.integration.smpp.session.SmppSessionFactoryBean"
id="inboundSession">
<property name="host" value="${smpp.host}" />
<property name="port" value="${smpp.port}" />
<property name="password" value="${smpp.password}" />
<property name="addressRange" value="33333" />
<property name="systemId" value="${smpp.systemId}" />
</bean>
<bean class="org.springframework.integration.smpp.inbound.SmppInboundGateway"
id="inboundGateway">
<property name="smppSession" ref="inboundSession" />
<property name="requestChannel" ref="in1" />
<property name="replyChannel" ref="out1" />
</bean>
<!-- <bean class="org.springframework.integration.smpp.inbound.SmppInboundChannelAdapter"
id="smppInboundChannelAdapter"> <property name="smppSession" ref="inboundSession"/>
<property name="channel" ref="inbound"/> </bean> <bean class="org.springframework.integration.smpp.outbound.SmppOutboundChannelAdapter"
id="smppOutboundChannelAdapter"> <property name="smppSession" ref="outboundSession"/>
</bean> -->
<!--<int:outbound-channel-adapter ref="smppOutboundChannelAdapter" channel="outbound"/> -->
<int:channel id="out1" />
<int:channel id="in1" />
<int:channel id="out2" />
<int:channel id="in2" />
</beans>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="smpp.properties" />
<context:annotation-config />
<bean
class="org.springframework.integration.smpp.session.SmppSessionFactoryBean"
id="session">
<property name="host" value="${smpp.host}" />
<property name="port" value="${smpp.port}" />
<property name="bindType" value="BIND_TX" />
<property name="password" value="${smpp.password}" />
<property name="systemId" value="${smpp.systemId}" />
</bean>
<bean
class="org.springframework.integration.smpp.outbound.SmppOutboundChannelAdapter"
id="adapter">
<property name="smppSession" ref="session" />
</bean>
<int:channel id="outboundSms" />
<int:outbound-channel-adapter channel="outboundSms"
ref="adapter" />
</beans>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="smpp.properties" />
<context:annotation-config />
<bean
class="org.springframework.integration.smpp.session.SmppSessionFactoryBean"
id="session">
<property name="host" value="${smpp.host}" />
<property name="port" value="${smpp.port}" />
<property name="password" value="${smpp.password}" />
<property name="systemId" value="${smpp.systemId}" />
<property name="bindType" value="BIND_TX" />
</bean>
<bean
class="org.springframework.integration.smpp.outbound.SmppOutboundGateway"
id="adapter">
<property name="smppSession" ref="session" />
</bean>
<int:channel id="outboundSms" />
<int:outbound-channel-adapter channel="outboundSms"
ref="adapter" />
</beans>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="smpp.properties" />
<bean
class="org.springframework.integration.smpp.session.SmppSessionFactoryBean"
id="session">
<property name="host" value="${smpp.host}" />
<property name="port" value="${smpp.port}" />
<property name="password" value="${smpp.password}" />
<property name="systemId" value="${smpp.systemId}" />
<property name="bindType" value="BIND_TX" />
<property name="autoStartup" value="true" />
</bean>
</beans>

View File

@@ -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

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-smpp="http://www.springframework.org/schema/integration/smpp"
xsi:schemaLocation="
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/smpp http://www.springframework.org/schema/integration/smpp/spring-integration-smpp.xsd">
<int:channel id="out">
<int:queue capacity="1000" />
</int:channel>
<bean id="session"
class="org.springframework.integration.smpp.config.xml.MockSmppSessionFactory"
factory-method="getInSmppSession">
</bean>
<int-smpp:inbound-channel-adapter id="smppInboundChannelAdapter"
auto-startup="true" channel="out">
<int-smpp:session ref="session" />
</int-smpp:inbound-channel-adapter>
</beans>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-smpp="http://www.springframework.org/schema/integration/smpp"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/smpp http://www.springframework.org/schema/integration/smpp/spring-integration-smpp.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- smpp configuration properties to test the gateway against real smpp
connection -->
<context:property-placeholder location="smpp.properties" />
<int:channel id="requestChannel" />
<int:channel id="replyChannel" />
<int:channel id="errorChannel" />
<bean id="inboundMapper" class="org.easymock.EasyMock"
factory-method="createNiceMock">
<constructor-arg
value="org.springframework.integration.mapping.InboundMessageMapper" />
</bean>
<bean id="outboundMapper" class="org.easymock.EasyMock"
factory-method="createNiceMock">
<constructor-arg
value="org.springframework.integration.mapping.OutboundMessageMapper" />
</bean>
<int-smpp:inbound-gateway request-channel="requestChannel"
reply-channel="replyChannel" error-channel="errorChannel"
source-address="123456789" source-ton="INTERNATIONAL" reply-mapper="outboundMapper"
request-mapper="inboundMapper" reply-timeout="5000" request-timeout="10000"
id="smppInboundGateway">
<int-smpp:session>
<!-- uncomment below to test with real connection -->
<!--<bean class="org.springframework.integration.smpp.session.SmppSessionFactoryBean"
id="session"> <property name="host" value="${smpp.host}"/> <property name="port"
value="${smpp.port}"/> <property name="bindType" value="BIND_RX"/> <property
name="password" value="${smpp.password}"/> <property name="systemId" value="${smpp.systemId}"
/> </bean> -->
<!-- comment below to test with real connection -->
<bean
class="org.springframework.integration.smpp.config.xml.MockSmppSessionFactory"
factory-method="getInSmppSession">
</bean>
</int-smpp:session>
</int-smpp:inbound-gateway>
</beans>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-smpp="http://www.springframework.org/schema/integration/smpp"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/smpp http://www.springframework.org/schema/integration/smpp/spring-integration-smpp.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- smpp configuration properties to test the gateway against real smpp
connection -->
<context:property-placeholder location="smpp.properties" />
<!-- uncomment below to test with real connection -->
<!--<bean class="org.springframework.integration.smpp.session.SmppSessionFactoryBean"
id="session"> <property name="host" value="${smpp.host}"/> <property name="port"
value="${smpp.port}"/> <property name="bindType" value="BIND_TRX"/> <property
name="password" value="${smpp.password}"/> <property name="systemId" value="${smpp.systemId}"
/> </bean> -->
<bean id="messagingTemplate" class="org.springframework.integration.core.MessagingTemplate">
<property name="receiveTimeout" value="5000" />
</bean>
<int:channel id="target" />
<bean id="session"
class="org.springframework.integration.smpp.config.xml.MockSmppSessionFactory"
factory-method="getOutSmppSession">
</bean>
<int-smpp:outbound-channel-adapter
id="smppOutboundChannelAdapter" smpp-session-ref="session"
source-address="12345" source-ton="SUBSCRIBER_NUMBER" channel="target">
</int-smpp:outbound-channel-adapter>
</beans>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-smpp="http://www.springframework.org/schema/integration/smpp"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/smpp http://www.springframework.org/schema/integration/smpp/spring-integration-smpp.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- smpp configuration properties to test the gateway against real smpp
connection -->
<context:property-placeholder location="smpp.properties" />
<int:channel id="in" />
<int:channel id="out" />
<bean id="timeFormatter" class="org.easymock.EasyMock"
factory-method="createNiceMock">
<constructor-arg value="org.jsmpp.util.TimeFormatter" />
</bean>
<int-smpp:outbound-gateway id="smppOutboundGateway"
source-address="123456789" source-ton="NETWORK_SPECIFIC" order="17"
request-channel="in" reply-channel="out" reply-timeout="100"
time-formatter="timeFormatter">
<int-smpp:session>
<!-- uncomment below to test with real connection -->
<!--<bean class="org.springframework.integration.smpp.session.SmppSessionFactoryBean"
id="session"> <property name="host" value="${smpp.host}"/> <property name="port"
value="${smpp.port}"/> <property name="bindType" value="BIND_TX"/> <property
name="password" value="${smpp.password}"/> <property name="systemId" value="${smpp.systemId}"
/> </bean> -->
<!-- comment below to test with real connection -->
<bean
class="org.springframework.integration.smpp.config.xml.MockSmppSessionFactory"
factory-method="getOutSmppSession" />
</int-smpp:session>
</int-smpp:outbound-gateway>
</beans>

View File

@@ -0,0 +1,7 @@
smpp.host=127.0.0.1
smpp.systemId=pavel
smpp.password=wpsd
smpp.port=2775
test.dst.number=123456